From 0e0c3fb08f73951e40cef464b42e2354af957fb8 Mon Sep 17 00:00:00 2001
From: IvanIhnatsiuk
Date: Thu, 11 Dec 2025 00:06:34 +0100
Subject: [PATCH 1/8] fix: set single attributed string to text view
---
example/src/App.tsx | 74 +++++
ios/EnrichedTextInputView.h | 25 +-
ios/EnrichedTextInputView.mm | 32 +-
ios/inputParser/InputParser.mm | 299 ++++++++++++------
ios/styles/BlockQuoteStyle.mm | 96 ++++--
ios/styles/BoldStyle.mm | 79 ++---
ios/styles/CodeBlockStyle.mm | 102 +++---
ios/styles/HeadingStyleBase.mm | 72 +++--
ios/styles/ImageAttachment.h | 10 +
ios/styles/ImageAttachment.mm | 33 ++
ios/styles/ImageStyle.mm | 134 ++++----
ios/styles/InlineCodeStyle.mm | 142 ++++-----
ios/styles/ItalicStyle.mm | 73 ++---
ios/styles/LinkStyle.mm | 148 +++++++--
ios/styles/MediaAttachment.h | 23 ++
ios/styles/MediaAttachment.mm | 33 ++
ios/styles/MentionStyle.mm | 97 ++++++
ios/styles/OrderedListStyle.mm | 107 +++++--
ios/styles/StrikethroughStyle.mm | 35 ++-
ios/styles/UnderlineStyle.mm | 34 +-
ios/styles/UnorderedListStyle.mm | 93 ++++--
ios/utils/BaseStyleProtocol.h | 5 +-
ios/utils/OccurenceUtils.h | 103 ++++---
ios/utils/OccurenceUtils.mm | 513 +++++++++++++++++++------------
ios/utils/ParagraphsUtils.h | 2 +
ios/utils/ParagraphsUtils.mm | 50 +++
ios/utils/StyleHeaders.h | 14 +-
ios/utils/TextInsertionUtils.h | 16 +-
ios/utils/TextInsertionUtils.mm | 23 ++
29 files changed, 1677 insertions(+), 790 deletions(-)
create mode 100644 ios/styles/ImageAttachment.h
create mode 100644 ios/styles/ImageAttachment.mm
create mode 100644 ios/styles/MediaAttachment.h
create mode 100644 ios/styles/MediaAttachment.mm
diff --git a/example/src/App.tsx b/example/src/App.tsx
index d36ac903a..a4587fba7 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -70,6 +70,78 @@ const DEBUG_SCROLLABLE = false;
// See: https://github.com/software-mansion/react-native-enriched/issues/229
const ANDROID_EXPERIMENTAL_SYNCHRONOUS_EVENTS = false;
+const generateHugeHtml = (repeat = 10) => {
+ const parts: string[] = [];
+ parts.push('');
+
+ // small helper to make deterministic colors
+ const colorAt = (i: number) => {
+ const r = (37 * (i + 1)) % 256;
+ const g = (83 * (i + 7)) % 256;
+ const b = (199 * (i + 13)) % 256;
+ const toHex = (n: number) => n.toString(16).padStart(2, '0');
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
+ };
+
+ for (let i = 0; i < repeat; i++) {
+ const col = colorAt(i);
+ const imgW = 200 + (i % 5) * 40;
+ const imgH = 100 + (i % 3) * 30;
+
+ parts.push(
+ // Headings
+ `\nSection ${i + 1} `,
+ `\nSubsection ${i + 1}.1 `,
+ `\nTopic ${i + 1}.1.a `,
+
+ // Paragraph with mixed inline styles
+ `\nThis is a bold and italic paragraph with underline , ` +
+ `strike , inline_code_${i}, ` +
+ `a link ${i} , ` +
+ ` , ` +
+ ` , ` +
+ `colored text ${col} and some plain text to bulk it up.
`,
+
+ // Line break
+ `\n `,
+
+ // Unordered list
+ `\n`,
+ `\nbullet A ${i} `,
+ `\nbullet B ${i} `,
+ `\nbullet C ${i} `,
+ `\n `,
+
+ // Ordered list
+ `\n`,
+ `\nstep 1.${i} `,
+ `\nstep 2.${i} `,
+ `\nstep 3.${i} `,
+ `\n `,
+
+ // Blockquote
+ `\n`,
+ `\n“Blockquote line 1 for ${i}.”
`,
+ `\n“Blockquote line 2 for ${i}.”
`,
+ `\n `,
+
+ // Code block (escaped characters)
+ `\n`,
+ `\nfor (let k = 0; k < ${i % 7}; k++) { console.log("block_${i}"); }
`,
+ `\n `,
+
+ // Image (self-closing)
+ `\n
`
+ );
+ }
+
+ parts.push('\n');
+ return parts.join('');
+};
+
+const initialHugeHtml = generateHugeHtml();
+console.log(initialHugeHtml.length);
+
export default function App() {
const [isChannelPopupOpen, setIsChannelPopupOpen] = useState(false);
const [isUserPopupOpen, setIsUserPopupOpen] = useState(false);
@@ -82,6 +154,7 @@ export default function App() {
const [stylesState, setStylesState] = useState(DEFAULT_STYLE);
const [currentLink, setCurrentLink] =
useState(DEFAULT_LINK_STATE);
+ const [visible, setVisible] = useState(false);
const ref = useRef(null);
@@ -287,6 +360,7 @@ export default function App() {
style={styles.container}
contentContainerStyle={styles.content}
>
+ setVisible(!visible)} />
Enriched Text Input
-#import
+#import "MediaAttachment.h"
#ifndef EnrichedTextInputViewNativeComponent_h
#define EnrichedTextInputViewNativeComponent_h
NS_ASSUME_NONNULL_BEGIN
-@interface EnrichedTextInputView : RCTViewComponentView {
-@public
- InputTextView *textView;
-@public
- NSRange recentlyChangedRange;
-@public
- InputConfig *config;
-@public
- InputParser *parser;
-@public
- NSMutableDictionary *defaultTypingAttributes;
-@public
- NSDictionary> *stylesDict;
+@interface EnrichedTextInputView : RCTViewComponentView {
+ @public InputTextView *textView;
+ @public NSRange recentlyChangedRange;
+ @public InputConfig *config;
+ @public InputParser *parser;
+ @public NSMutableDictionary *defaultTypingAttributes;
+ @public NSDictionary> *stylesDict;
NSDictionary *> *conflictingStyles;
NSDictionary *> *blockingStyles;
@public
BOOL blockEmitting;
}
+@property (nonatomic, strong) NSDictionary *> *conflictingStyles;
+@property (nonatomic, strong) NSDictionary *> *blockingStyles;
- (CGSize)measureSize:(CGFloat)maxWidth;
- (void)emitOnLinkDetectedEvent:(NSString *)text
url:(NSString *)url
diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm
index c84dd532f..012b9e05e 100644
--- a/ios/EnrichedTextInputView.mm
+++ b/ios/EnrichedTextInputView.mm
@@ -37,6 +37,7 @@ @implementation EnrichedTextInputView {
UILabel *_placeholderLabel;
UIColor *_placeholderColor;
BOOL _emitFocusBlur;
+ BOOL _initialMount;
}
// MARK: - Component utils
@@ -218,6 +219,33 @@ - (void)setupPlaceholderLabel {
_placeholderLabel.hidden = YES;
}
+- (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];
+}
+
// MARK: - Props
- (void)updateProps:(Props::Shared const &)props
@@ -1416,10 +1444,10 @@ - (void)_performRelayout {
});
}
+
- (void)didMoveToWindow {
[super didMoveToWindow];
- // used to run all lifecycle callbacks
- [self anyTextMayHaveBeenModified];
+ [self layoutIfNeeded];
}
// MARK: - UITextView delegate methods
diff --git a/ios/inputParser/InputParser.mm b/ios/inputParser/InputParser.mm
index 7ad395ede..bee580edd 100644
--- a/ios/inputParser/InputParser.mm
+++ b/ios/inputParser/InputParser.mm
@@ -492,103 +492,184 @@ - (NSString *)tagContentForStyle:(NSNumber *)style
return @"";
}
-- (void)replaceWholeFromHtml:(NSString *_Nonnull)html {
- NSArray *processingResult = [self getTextAndStylesFromHtml:html];
- NSString *plainText = (NSString *)processingResult[0];
- NSArray *stylesInfo = (NSArray *)processingResult[1];
-
- // reset the text first and reset typing attributes
- _input->textView.text = @"";
- _input->textView.typingAttributes = _input->defaultTypingAttributes;
-
- // set new text
- _input->textView.text = plainText;
-
- // re-apply the styles
- [self applyProcessedStyles:stylesInfo
- offsetFromBeginning:0
- plainTextLength:plainText.length];
+- (void)replaceWholeFromHtml:(NSString * _Nonnull)html {
+ NSArray *processingResult = [self getTextAndStylesFromHtml:html];
+ NSString *plainText = (NSString *)processingResult[0];
+ NSArray *stylesInfo = (NSArray *)processingResult[1];
+
+ NSMutableAttributedString *newAttr =
+ [[NSMutableAttributedString alloc] initWithString:plainText
+ attributes:_input->defaultTypingAttributes];
+
+ [self applyProcessedStyles:stylesInfo
+ toAttributedString:newAttr
+ offsetFromBeginning:0];
+
+ NSTextStorage *storage = _input->textView.textStorage;
+
+ [storage setAttributedString:newAttr];
+
+ _input->textView.typingAttributes = _input->defaultTypingAttributes;
+ [_input anyTextMayHaveBeenModified];
+}
+
+- (BOOL)styleType:(NSNumber *)type existsInStyles:(NSArray *)styles atRange:(NSRange)r {
+ for (NSArray *entry in styles) {
+ NSNumber *otherType = entry[0];
+ StylePair *pair = entry[1];
+ if ([otherType isEqualToNumber:type] &&
+ NSEqualRanges(pair.rangeValue.rangeValue, r)) {
+ return YES;
+ }
+ }
+ return NO;
}
-- (void)replaceFromHtml:(NSString *_Nonnull)html range:(NSRange)range {
- NSArray *processingResult = [self getTextAndStylesFromHtml:html];
- NSString *plainText = (NSString *)processingResult[0];
- NSArray *stylesInfo = (NSArray *)processingResult[1];
-
- // we can use ready replace util
- [TextInsertionUtils replaceText:plainText
- at:range
- additionalAttributes:nil
- input:_input
- withSelection:YES];
-
- [self applyProcessedStyles:stylesInfo
- offsetFromBeginning:range.location
- plainTextLength:plainText.length];
+- (void)removeStyleType:(NSNumber *)type
+ fromStyles:(NSMutableArray *)styles
+ atRange:(NSRange)r
+{
+ NSMutableArray *remove = [NSMutableArray array];
+
+ for (NSArray *entry in styles) {
+ NSNumber *otherType = entry[0];
+ StylePair *pair = entry[1];
+
+ if ([otherType isEqualToNumber:type] &&
+ NSEqualRanges(pair.rangeValue.rangeValue, r)) {
+ [remove addObject:entry];
+ }
+ }
+
+ [styles removeObjectsInArray:remove];
}
-- (void)insertFromHtml:(NSString *_Nonnull)html location:(NSInteger)location {
- NSArray *processingResult = [self getTextAndStylesFromHtml:html];
- NSString *plainText = (NSString *)processingResult[0];
- NSArray *stylesInfo = (NSArray *)processingResult[1];
-
- // same here, insertion utils got our back
- [TextInsertionUtils insertText:plainText
- at:location
- additionalAttributes:nil
- input:_input
- withSelection:YES];
-
- [self applyProcessedStyles:stylesInfo
- offsetFromBeginning:location
- plainTextLength:plainText.length];
+- (BOOL)shouldApplyStyle:(StyleType)styleType
+ existingStyles:(NSArray *)styles
+ at:(NSRange)range
+{
+ NSArray *blocking = _input->blockingStyles[@(styleType)];
+ for (NSNumber *b in blocking) {
+ if ([self styleType:b existsInStyles:styles atRange:range]) {
+ return NO;
+ }
+ }
+
+ NSArray *conflicting = _input->conflictingStyles[@(styleType)];
+ for (NSNumber *c in conflicting) {
+ [self removeStyleType:c fromStyles:styles atRange:range];
+ }
+
+ return YES;
}
- (void)applyProcessedStyles:(NSArray *)processedStyles
- offsetFromBeginning:(NSInteger)offset
- plainTextLength:(NSUInteger)plainTextLength {
- for (NSArray *arr in processedStyles) {
- // unwrap all info from processed style
- NSNumber *styleType = (NSNumber *)arr[0];
- StylePair *stylePair = (StylePair *)arr[1];
- id baseStyle = _input->stylesDict[styleType];
- // range must be taking offest into consideration because processed styles'
- // ranges are relative to only the new text while we need absolute ranges
- // relative to the whole existing text
- NSRange styleRange =
- NSMakeRange(offset + [stylePair.rangeValue rangeValue].location,
- [stylePair.rangeValue rangeValue].length);
-
- // of course any changes here need to take blocks and conflicts into
- // consideration
- if ([_input handleStyleBlocksAndConflicts:[[baseStyle class] getStyleType]
- range:styleRange]) {
- if ([styleType isEqualToNumber:@([LinkStyle getStyleType])]) {
- NSString *text =
- [_input->textView.textStorage.string substringWithRange:styleRange];
- NSString *url = (NSString *)stylePair.styleValue;
- BOOL isManual = ![text isEqualToString:url];
- [((LinkStyle *)baseStyle) addLink:text
- url:url
- range:styleRange
- manual:isManual];
- } else if ([styleType isEqualToNumber:@([MentionStyle getStyleType])]) {
- MentionParams *params = (MentionParams *)stylePair.styleValue;
- [((MentionStyle *)baseStyle) addMentionAtRange:styleRange
- params:params];
- } else if ([styleType isEqualToNumber:@([ImageStyle getStyleType])]) {
- ImageData *imgData = (ImageData *)stylePair.styleValue;
- [((ImageStyle *)baseStyle) addImageAtRange:styleRange
- imageData:imgData
- withSelection:NO];
- } else {
- BOOL shouldAddTypingAttr =
- styleRange.location + styleRange.length == plainTextLength;
- [baseStyle addAttributes:styleRange withTypingAttr:shouldAddTypingAttr];
- }
+ toAttributedString:(NSMutableAttributedString *)attributedString
+ offsetFromBeginning:(NSInteger)offset
+{
+ NSArray *sorted =
+ [processedStyles sortedArrayUsingComparator:^NSComparisonResult(NSArray *a, NSArray *b) {
+ StylePair *pa = a[1];
+ StylePair *pb = b[1];
+
+ NSInteger la = offset + pa.rangeValue.rangeValue.location;
+ NSInteger lb = offset + pb.rangeValue.rangeValue.location;
+
+ if (la > lb) return NSOrderedAscending;
+ if (la < lb) return NSOrderedDescending;
+ return NSOrderedSame;
+ }];
+
+ [attributedString beginEditing];
+
+ for (NSArray *arr in sorted) {
+ NSNumber *styleType = arr[0];
+ StylePair *stylePair = arr[1];
+ id style = _input->stylesDict[styleType];
+
+ NSRange r = NSMakeRange(
+ offset + stylePair.rangeValue.rangeValue.location,
+ stylePair.rangeValue.rangeValue.length
+ );
+ if ([styleType isEqualToNumber:@([LinkStyle getStyleType])]) {
+ NSString *text = [attributedString.string substringWithRange:r];
+ NSString *url = stylePair.styleValue;
+ BOOL isManual = [text isEqualToString: url];
+ [(LinkStyle *)style addLinkInAttributedString:attributedString range:r text:text url:url manual:isManual];
+
+ } else if ([styleType isEqualToNumber:@([MentionStyle getStyleType])]) {
+ [(MentionStyle *)style addMentionInAttributedString:attributedString
+ range:r
+ params:stylePair.styleValue];
+ } else if ([styleType isEqualToNumber:@([ImageStyle getStyleType])]) {
+ [(ImageStyle *)style addImageInAttributedString:attributedString
+ range:r
+ imageData:stylePair.styleValue];
+ } else {
+ [style addAttributesInAttributedString:attributedString range:r];
+ }
}
- }
- [_input anyTextMayHaveBeenModified];
+
+ [attributedString endEditing];
+}
+
+- (void)replaceFromHtml:(NSString * _Nonnull)html range:(NSRange)range
+{
+ if (!html || range.length == 0) return;
+ NSArray *processingResult = [self getTextAndStylesFromHtml:html];
+ if (processingResult.count < 2) return;
+
+ NSString *plainText = processingResult[0];
+ NSArray *stylesInfo = processingResult[1];
+
+ NSMutableAttributedString *inserted =
+ [[NSMutableAttributedString alloc] initWithString:plainText
+ attributes:_input->defaultTypingAttributes];
+ [self applyProcessedStyles:stylesInfo
+ toAttributedString:inserted
+ offsetFromBeginning:0];
+
+ NSTextStorage *storage = _input->textView.textStorage;
+
+ if (range.location > storage.length)
+ range.location = storage.length;
+
+ if (NSMaxRange(range) > storage.length)
+ range.length = storage.length - range.location;
+
+ [storage beginEditing];
+ [storage replaceCharactersInRange:range withAttributedString:inserted];
+ [storage endEditing];
+ _input->textView.selectedRange = NSMakeRange(range.location + inserted.length, 0);
+
+ _input->textView.typingAttributes = _input->defaultTypingAttributes;
+
+ [_input anyTextMayHaveBeenModified];
+}
+
+
+- (void)insertFromHtml:(NSString * _Nonnull)html location:(NSInteger)location {
+ NSArray *processingResult = [self getTextAndStylesFromHtml:html];
+ NSString *plainText = processingResult[0];
+ NSArray *stylesInfo = processingResult[1];
+
+ // Base attributed segment for inserted text
+ NSMutableAttributedString *inserted =
+ [[NSMutableAttributedString alloc] initWithString:plainText
+ attributes:_input->defaultTypingAttributes];
+ [self applyProcessedStyles:stylesInfo
+ toAttributedString:inserted
+ offsetFromBeginning:0];
+
+ if (location > _input->textView.textStorage.length) {
+ location = _input->textView.textStorage.length;
+ }
+
+ [_input->textView.textStorage beginEditing];
+ [_input->textView.textStorage insertAttributedString:inserted atIndex:location];
+ [_input->textView.textStorage endEditing];
+
+ _input->textView.selectedRange = NSMakeRange(location + inserted.length, 0);
}
- (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html {
@@ -766,6 +847,42 @@ - (void)finalizeTagEntry:(NSMutableString *)tagName
[ongoingTags removeObjectForKey:tagName];
}
+- (void)sanitizeStyles:(NSMutableArray *)styles {
+ NSMutableArray *toRemove = [NSMutableArray array];
+ for (NSArray *entry in [styles copy]) {
+ NSNumber *styleType = entry[0];
+ StylePair *pair = entry[1];
+ NSRange r = pair.rangeValue.rangeValue;
+
+ BOOL shouldRemove = NO;
+ NSArray *blocking = _input.blockingStyles[styleType];
+ for (NSNumber *bType in blocking) {
+ if ([self styleType:bType existsInStyles:styles atRange:r]) {
+ shouldRemove = YES;
+ break;
+ }
+ }
+
+ if (shouldRemove) {
+ [toRemove addObject:entry];
+ continue;
+ }
+
+ NSArray *conflicting = _input.conflictingStyles[styleType];
+ for (NSNumber *cType in conflicting) {
+ [self removeStyleType:cType fromStyles:styles atRange:r];
+ }
+
+ if (shouldRemove) {
+ [toRemove addObject:entry];
+ }
+ }
+
+ [styles removeObjectsInArray:toRemove];
+}
+
+
+
- (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml {
NSMutableString *plainText = [[NSMutableString alloc] initWithString:@""];
NSMutableDictionary *ongoingTags = [[NSMutableDictionary alloc] init];
@@ -1064,8 +1181,8 @@ - (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml {
[styleArr addObject:stylePair];
[processedStyles addObject:styleArr];
}
-
- return @[ plainText, processedStyles ];
+ [self sanitizeStyles:processedStyles];
+ return @[plainText, processedStyles];
}
@end
diff --git a/ios/styles/BlockQuoteStyle.mm b/ios/styles/BlockQuoteStyle.mm
index f8d039cd9..a4a0c21e5 100644
--- a/ios/styles/BlockQuoteStyle.mm
+++ b/ios/styles/BlockQuoteStyle.mm
@@ -42,12 +42,41 @@ - (void)applyStyle:(NSRange)range {
}
}
-- (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
+- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesInAttributedString:attributedString range:range];
+ // if we fill empty lines with zero width spaces, we need to offset later ranges
+ NSInteger offset = 0;
+
+ 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: [attributedString.string characterAtIndex:pRange.location]])
+ ) {
+ [TextInsertionUtils insertTextInAttributedString:@"\u200B" at:pRange.location additionalAttributes:nullptr attributedString:attributedString];
+ pRange = NSMakeRange(pRange.location, pRange.length + 1);
+ offset += 1;
+ }
+
+ [attributedString enumerateAttribute:NSParagraphStyleAttributeName inRange:pRange options:0
+ usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
+ NSMutableParagraphStyle *pStyle = value ? [(NSParagraphStyle *)value mutableCopy]
+ : [NSMutableParagraphStyle new];
+ pStyle.headIndent = [self getHeadIndent];
+ pStyle.firstLineHeadIndent = [self getHeadIndent];
+ NSMutableDictionary *typingAttrs = [_input->textView.typingAttributes mutableCopy];
+ typingAttrs[NSParagraphStyleAttributeName] = pStyle;
+ [attributedString addAttribute:NSParagraphStyleAttributeName value:pStyle range:range];
+ }
+ ];
+ }
+}
+
+- (void)addAttributes:(NSRange)range {
+ 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;
@@ -122,30 +151,26 @@ - (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) {
+- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ NSArray *paragraphs = [ParagraphsUtils getNonNewlineRangesInAttributedString:attributedString 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];
- }];
+ [attributedString enumerateAttribute:NSParagraphStyleAttributeName inRange:pRange options:0
+ usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
+ NSMutableParagraphStyle *pStyle = value ? [(NSParagraphStyle *)value mutableCopy]
+ : [NSMutableParagraphStyle new];
+ pStyle.headIndent = 0;
+ pStyle.firstLineHeadIndent = 0;
+ [attributedString addAttribute:NSParagraphStyleAttributeName value:pStyle range:range];
+ }
+ ];
}
+}
+- (void)removeAttributes:(NSRange)range {
+ [self removeAttributesInAttributedString: _input->textView.textStorage range:range];
+
// also remove typing attributes
NSMutableDictionary *typingAttrs =
[_input->textView.typingAttributes mutableCopy];
@@ -193,14 +218,17 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
pStyle.textLists.count == 0;
}
+- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ return [OccurenceUtils detect:NSParagraphStyleAttributeName inString:attributedString inRange:range
+ withCondition: ^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value :range];
+ }
+ ];
+}
+
- (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];
- }];
+ if(range.length >= 1) {
+ return [self detectStyleInAttributedString: _input->textView.textStorage range:range];
} else {
return [OccurenceUtils detect:NSParagraphStyleAttributeName
withInput:_input
diff --git a/ios/styles/BoldStyle.mm b/ios/styles/BoldStyle.mm
index aae3b400b..73e544086 100644
--- a/ios/styles/BoldStyle.mm
+++ b/ios/styles/BoldStyle.mm
@@ -31,22 +31,25 @@ - (void)applyStyle:(NSRange)range {
}
}
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
+- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attr
+ range:(NSRange)range
+{
+ [attr enumerateAttribute:NSFontAttributeName
+ inRange:range
+ options:0
+ usingBlock:^(id value, NSRange subrange, BOOL *stop) {
+
+ UIFont *font = (UIFont *)value;
+ if (font != nil) {
+ UIFont *newFont = [font setBold];
+ [attr addAttribute:NSFontAttributeName value:newFont range:subrange];
+ }
+ }];
+}
+
+- (void)addAttributes:(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 setBold];
- [_input->textView.textStorage addAttribute:NSFontAttributeName
- value:newFont
- range:range];
- }
- }];
+ [self addAttributesInAttributedString: _input->textView.textStorage range:range];
[_input->textView.textStorage endEditing];
}
@@ -61,22 +64,23 @@ - (void)addTypingAttributes {
}
}
+- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ [attributedString enumerateAttribute:NSFontAttributeName
+ inRange:range
+ options:0
+ usingBlock:^(id value, NSRange subrange, BOOL *stop) {
+
+ UIFont *font = (UIFont *)value;
+ if (font != nil) {
+ UIFont *newFont = [font removeBold];
+ [attributedString addAttribute:NSFontAttributeName value:newFont range:subrange];
+ }
+ }];
+}
+
- (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];
- }
- }];
+ [self removeAttributesInAttributedString: _input->textView.textStorage range:range];
[_input->textView.textStorage endEditing];
}
@@ -119,14 +123,17 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
![self boldHeadingConflictsInRange:range type:H3];
}
+- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ return [OccurenceUtils detect:NSFontAttributeName inString:attributedString inRange:range
+ withCondition: ^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value :range];
+ }
+ ];
+}
+
- (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];
- }];
+ if(range.length >= 1) {
+ return [self detectStyleInAttributedString: _input->textView.textStorage range: range];
} else {
return [OccurenceUtils detect:NSFontAttributeName
withInput:_input
diff --git a/ios/styles/CodeBlockStyle.mm b/ios/styles/CodeBlockStyle.mm
index 1f5aa9976..d034ba8b8 100644
--- a/ios/styles/CodeBlockStyle.mm
+++ b/ios/styles/CodeBlockStyle.mm
@@ -36,14 +36,42 @@ - (void)applyStyle:(NSRange)range {
}
}
-- (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
+- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ NSTextList *codeBlockList = [[NSTextList alloc] initWithMarkerFormat:@"codeblock" options:0];
+ NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesInAttributedString:attributedString range:range];
+ // if we fill empty lines with zero width spaces, we need to offset later ranges
+ NSInteger offset = 0;
+
+ 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: [attributedString.string characterAtIndex:pRange.location]])
+ ) {
+ [TextInsertionUtils insertTextInAttributedString:@"\u200B" at:pRange.location additionalAttributes:nullptr attributedString:attributedString];
+ pRange = NSMakeRange(pRange.location, pRange.length + 1);
+ offset += 1;
+ }
+
+ [attributedString enumerateAttribute:NSParagraphStyleAttributeName inRange:pRange options:0
+ usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
+ NSMutableParagraphStyle *pStyle = value ? [(NSParagraphStyle *)value mutableCopy]
+ : [NSMutableParagraphStyle new];
+ NSMutableDictionary *typingAttrs = [_input->textView.typingAttributes mutableCopy];
+ pStyle.textLists = @[codeBlockList];
+ typingAttrs[NSParagraphStyleAttributeName] = pStyle;
+ [attributedString addAttribute:NSParagraphStyleAttributeName value:pStyle range:range];
+ }
+ ];
+ }
+}
+
+- (void)addAttributes:(NSRange)range {
+ 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;
@@ -115,32 +143,27 @@ - (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) {
+- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ NSArray *paragraphs = [ParagraphsUtils getNonNewlineRangesInAttributedString:attributedString 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.textLists = @[];
- [_input->textView.textStorage
- addAttribute:NSParagraphStyleAttributeName
- value:pStyle
- range:range];
- }];
+
+ [attributedString enumerateAttribute:NSParagraphStyleAttributeName inRange:pRange options:0
+ usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
+ NSMutableParagraphStyle *pStyle = [(NSParagraphStyle *)value mutableCopy];
+
+ pStyle.textLists = @[];
+ [attributedString addAttribute:NSParagraphStyleAttributeName value:pStyle range:range];
+ }
+ ];
}
+}
+
+- (void)removeAttributes:(NSRange)range {
+ [_input->textView.textStorage beginEditing];
+ [self removeAttributesInAttributedString: _input->textView.textStorage range:range];
[_input->textView.textStorage endEditing];
// also remove typing attributes
@@ -184,14 +207,17 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
isEqualToString:@"codeblock"];
}
+- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ return [OccurenceUtils detect:NSParagraphStyleAttributeName inString:attributedString inRange:range
+ withCondition: ^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value :range];
+ }
+ ];
+}
+
- (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];
- }];
+ if(range.length >= 1) {
+ return [self detectStyleInAttributedString:_input->textView.textStorage range:range];
} else {
return [OccurenceUtils detect:NSParagraphStyleAttributeName
withInput:_input
diff --git a/ios/styles/HeadingStyleBase.mm b/ios/styles/HeadingStyleBase.mm
index 6e86672f3..1101273b5 100644
--- a/ios/styles/HeadingStyleBase.mm
+++ b/ios/styles/HeadingStyleBase.mm
@@ -42,30 +42,26 @@ - (void)applyStyle:(NSRange)range {
// 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
- 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 setSize:[self getHeadingFontSize]];
- if ([self isHeadingBold]) {
- newFont = [newFont setBold];
- }
- [[self typedInput]->textView.textStorage
- addAttribute:NSFontAttributeName
- value:newFont
- range:range];
- }
- }];
+ [self addAttributesInAttributedString: [self typedInput]->textView.textStorage range:range];
[[self typedInput]->textView.textStorage endEditing];
-
+
// also toggle typing attributes
- if (withTypingAttr) {
- [self addTypingAttributes];
- }
+ [self addTypingAttributes];
+}
+
+- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ [attributedString 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 setSize:[self getHeadingFontSize]];
+ if([self isHeadingBold]) {
+ newFont = [newFont setBold];
+ }
+ [attributedString addAttribute:NSFontAttributeName value:newFont range:range];
+ }
+ }
+ ];
}
// will always be called on empty paragraphs so only typing attributes can be
@@ -86,6 +82,21 @@ - (void)addTypingAttributes {
}
}
+- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ NSRange paragraphRange = [attributedString.string paragraphRangeForRange:range];
+ [attributedString enumerateAttribute:NSFontAttributeName inRange:paragraphRange options:0
+ usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
+ if([self styleCondition:value :range]) {
+ UIFont *newFont = [(UIFont *)value setSize:[[[self typedInput]->config primaryFontSize] floatValue]];
+ if([self isHeadingBold]) {
+ newFont = [newFont removeBold];
+ }
+ [attributedString addAttribute:NSFontAttributeName value:newFont range:range];
+ }
+ }
+ ];
+}
+
// we need to remove the style from the whole paragraph
- (void)removeAttributes:(NSRange)range {
NSRange paragraphRange = [[self typedInput]->textView.textStorage.string
@@ -142,14 +153,17 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
return font != nullptr && font.pointSize == [self getHeadingFontSize];
}
+- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ return [OccurenceUtils detect:NSFontAttributeName inString:attributedString inRange:range
+ withCondition: ^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value :range];
+ }
+ ];
+}
+
- (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];
- }];
+ if(range.length >= 1) {
+ return [self detectStyleInAttributedString: [self typedInput]->textView.textStorage range:range];
} else {
return [OccurenceUtils detect:NSFontAttributeName
withInput:[self typedInput]
diff --git a/ios/styles/ImageAttachment.h b/ios/styles/ImageAttachment.h
new file mode 100644
index 000000000..481199532
--- /dev/null
+++ b/ios/styles/ImageAttachment.h
@@ -0,0 +1,10 @@
+#import "MediaAttachment.h"
+#import "ImageData.h"
+
+@interface ImageAttachment : MediaAttachment
+
+@property(nonatomic, strong) ImageData *imageData;
+
+- (instancetype)initWithImageData:(ImageData *)data;
+
+@end
diff --git a/ios/styles/ImageAttachment.mm b/ios/styles/ImageAttachment.mm
new file mode 100644
index 000000000..f97e840cf
--- /dev/null
+++ b/ios/styles/ImageAttachment.mm
@@ -0,0 +1,33 @@
+#import "ImageAttachment.h"
+
+@implementation ImageAttachment
+
+- (instancetype)initWithImageData:(ImageData *)data
+{
+ self = [super initWithURI:data.uri width:data.width height:data.height];
+ if (!self) return nil;
+
+ _imageData = data;
+
+ [self loadAsync];
+ return self;
+}
+
+- (void)loadAsync
+{
+ NSURL *url = [NSURL URLWithString:self.uri];
+ if (!url) return;
+
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
+ NSData *bytes = [NSData dataWithContentsOfURL:url];
+ UIImage *img = bytes ? [UIImage imageWithData:bytes] : nil;
+ if (!img) return;
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ self.image = img;
+ [self notifyUpdate];
+ });
+ });
+}
+
+@end
diff --git a/ios/styles/ImageStyle.mm b/ios/styles/ImageStyle.mm
index 2470d826d..8bae49244 100644
--- a/ios/styles/ImageStyle.mm
+++ b/ios/styles/ImageStyle.mm
@@ -2,6 +2,7 @@
#import "OccurenceUtils.h"
#import "StyleHeaders.h"
#import "TextInsertionUtils.h"
+#import "ImageAttachment.h"
// custom NSAttributedStringKey to differentiate the image
static NSString *const ImageAttributeName = @"ImageAttributeName";
@@ -32,15 +33,22 @@ - (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
// no-op for image
}
+- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ // no-op for image
+}
+
- (void)addTypingAttributes {
// no-op for image
}
+- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ [attributedString removeAttribute:ImageAttributeName range:range];
+ [attributedString removeAttribute:NSAttachmentAttributeName range:range];
+}
+
- (void)removeAttributes:(NSRange)range {
[_input->textView.textStorage beginEditing];
- [_input->textView.textStorage removeAttribute:ImageAttributeName range:range];
- [_input->textView.textStorage removeAttribute:NSAttachmentAttributeName
- range:range];
+ [self removeAttributesInAttributedString: _input->textView.textStorage range:range];
[_input->textView.textStorage endEditing];
}
@@ -65,14 +73,17 @@ - (BOOL)anyOccurence:(NSRange)range {
}];
}
+- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ return [OccurenceUtils detect:ImageAttributeName inString:attributedString inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value :range];
+ }
+ ];
+}
+
- (BOOL)detectStyle:(NSRange)range {
if (range.length >= 1) {
- return [OccurenceUtils detect:ImageAttributeName
- withInput:_input
- inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value:range];
- }];
+ return [self detectStyleInAttributedString: _input->textView.textStorage range:range];
} else {
return [OccurenceUtils detect:ImageAttributeName
withInput:_input
@@ -114,63 +125,74 @@ - (ImageData *)getImageDataAt:(NSUInteger)location {
- (void)addImageAtRange:(NSRange)range
imageData:(ImageData *)imageData
- withSelection:(BOOL)withSelection {
- UIImage *img = [self prepareImageFromUri:imageData.uri];
-
- NSDictionary *attributes = [@{
- NSAttachmentAttributeName : [self prepareImageAttachement:img
- width:imageData.width
- height:imageData.height],
- ImageAttributeName : imageData,
- } mutableCopy];
-
- // Use the Object Replacement Character for Image.
- // This tells TextKit "something non-text goes here".
- NSString *imagePlaceholder = @"\uFFFC";
-
- if (range.length == 0) {
- [TextInsertionUtils insertText:imagePlaceholder
- at:range.location
- additionalAttributes:attributes
- input:_input
- withSelection:withSelection];
- } else {
- [TextInsertionUtils replaceText:imagePlaceholder
- at:range
- additionalAttributes:attributes
- input:_input
- withSelection:withSelection];
- }
+ withSelection:(BOOL)withSelection
+{
+ if (!imageData) return;
+
+ ImageAttachment *attachment =
+ [[ImageAttachment alloc] initWithImageData:imageData];
+ attachment.delegate = _input;
+
+ NSDictionary *attrs = @{
+ NSAttachmentAttributeName : attachment,
+ ImageAttributeName : imageData
+ };
+
+ NSString *placeholderChar = @"\uFFFC";
+
+ if (range.length == 0) {
+ [TextInsertionUtils insertText:placeholderChar
+ at:range.location
+ additionalAttributes:attrs
+ input:_input
+ withSelection:withSelection];
+ } else {
+ [TextInsertionUtils replaceText:placeholderChar
+ at:range
+ additionalAttributes:attrs
+ input:_input
+ withSelection:withSelection];
+ }
}
- (void)addImage:(NSString *)uri width:(CGFloat)width height:(CGFloat)height {
- ImageData *data = [[ImageData alloc] init];
- data.uri = uri;
- data.width = width;
- data.height = height;
+ ImageData *data = [[ImageData alloc] init];
+ data.uri = uri;
+ data.width = width;
+ data.height = height;
- [self addImageAtRange:_input->textView.selectedRange
- imageData:data
- withSelection:YES];
+ [self addImageAtRange:_input->textView.selectedRange
+ imageData:data
+ withSelection:YES];
}
-- (NSTextAttachment *)prepareImageAttachement:(UIImage *)image
- width:(CGFloat)width
- height:(CGFloat)height {
+- (void)addImageInAttributedString:(NSMutableAttributedString *)string
+ range:(NSRange)range
+ imageData:(ImageData *)imageData
+{
+ if (!imageData) return;
- NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
- attachment.image = image;
- attachment.bounds = CGRectMake(0, 0, width, height);
+ ImageAttachment *attachment =
+ [[ImageAttachment alloc] initWithImageData:imageData];
+ attachment.delegate = _input;
- return attachment;
-}
+ NSMutableDictionary *attrs =
+ [_input->defaultTypingAttributes mutableCopy];
+ attrs[NSAttachmentAttributeName] = attachment;
+ attrs[ImageAttributeName] = imageData;
-- (UIImage *)prepareImageFromUri:(NSString *)uri {
- NSURL *url = [NSURL URLWithString:uri];
- NSData *imgData = [NSData dataWithContentsOfURL:url];
- UIImage *image = [UIImage imageWithData:imgData];
+ NSAttributedString *imgString =
+ [[NSAttributedString alloc] initWithString:@"\uFFFC"
+ attributes:attrs];
- return image;
+ if (range.length == 0) {
+ [string insertAttributedString:imgString
+ atIndex:range.location];
+ } else {
+ [string replaceCharactersInRange:range
+ withAttributedString:imgString];
+ }
}
+
@end
diff --git a/ios/styles/InlineCodeStyle.mm b/ios/styles/InlineCodeStyle.mm
index 3bc7d0ab8..0fd7f8e3a 100644
--- a/ios/styles/InlineCodeStyle.mm
+++ b/ios/styles/InlineCodeStyle.mm
@@ -33,7 +33,23 @@ - (void)applyStyle:(NSRange)range {
}
}
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
+- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)currentRange {
+ [attributedString addAttribute:NSBackgroundColorAttributeName value:[[_input->config inlineCodeBgColor] colorWithAlphaIfNotTransparent:0.4] range:currentRange];
+ [attributedString addAttribute:NSForegroundColorAttributeName value:[_input->config inlineCodeFgColor] range:currentRange];
+ [attributedString addAttribute:NSUnderlineColorAttributeName value:[_input->config inlineCodeFgColor] range:currentRange];
+ [attributedString addAttribute:NSStrikethroughColorAttributeName value:[_input->config inlineCodeFgColor] range:currentRange];
+ [attributedString enumerateAttribute:NSFontAttributeName inRange:currentRange options:0
+ usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
+ UIFont *font = (UIFont *)value;
+ if(font != nullptr) {
+ UIFont *newFont = [[[_input->config monospacedFont] withFontTraits:font] setSize:font.pointSize];
+ [attributedString addAttribute:NSFontAttributeName value:newFont range:range];
+ }
+ }
+ ];
+}
+
+- (void)addAttributes:(NSRange)range {
// we don't want to apply inline code to newline characters, it looks bad
NSArray *nonNewlineRanges =
[ParagraphsUtils getNonNewlineRangesIn:_input->textView range:range];
@@ -41,41 +57,7 @@ - (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
for (NSValue *value in nonNewlineRanges) {
NSRange currentRange = [value rangeValue];
[_input->textView.textStorage beginEditing];
-
- [_input->textView.textStorage
- addAttribute:NSBackgroundColorAttributeName
- value:[[_input->config inlineCodeBgColor]
- colorWithAlphaIfNotTransparent:0.4]
- range:currentRange];
- [_input->textView.textStorage
- addAttribute:NSForegroundColorAttributeName
- value:[_input->config inlineCodeFgColor]
- range:currentRange];
- [_input->textView.textStorage
- addAttribute:NSUnderlineColorAttributeName
- value:[_input->config inlineCodeFgColor]
- range:currentRange];
- [_input->textView.textStorage
- addAttribute:NSStrikethroughColorAttributeName
- value:[_input->config inlineCodeFgColor]
- range:currentRange];
- [_input->textView.textStorage
- enumerateAttribute:NSFontAttributeName
- inRange:currentRange
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- UIFont *font = (UIFont *)value;
- if (font != nullptr) {
- UIFont *newFont = [[[_input->config monospacedFont]
- withFontTraits:font] setSize:font.pointSize];
- [_input->textView.textStorage
- addAttribute:NSFontAttributeName
- value:newFont
- range:range];
- }
- }];
-
+ [self addAttributesInAttributedString: _input->textView.textStorage range: currentRange];
[_input->textView.textStorage endEditing];
}
}
@@ -99,36 +81,25 @@ - (void)addTypingAttributes {
_input->textView.typingAttributes = newTypingAttrs;
}
+- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ [attributedString removeAttribute:NSBackgroundColorAttributeName range:range];
+ [attributedString addAttribute:NSForegroundColorAttributeName value:[_input->config primaryColor] range:range];
+ [attributedString addAttribute:NSUnderlineColorAttributeName value:[_input->config primaryColor] range:range];
+ [attributedString addAttribute:NSStrikethroughColorAttributeName value:[_input->config primaryColor] range:range];
+ [attributedString 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];
+ [attributedString addAttribute:NSFontAttributeName value:newFont range:range];
+ }
+ }
+ ];
+}
+
- (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];
- }
- }];
-
+ [self removeAttributesInAttributedString: _input->textView.textStorage range:range];
[_input->textView.textStorage endEditing];
}
@@ -177,29 +148,30 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
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)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ // 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 inString:attributedString inRange:currentRange
+ withCondition: ^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value :range];
+ }
+ ];
+ detected = detected && currentDetected;
+ }
- 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];
- }];
- detected = detected && currentDetected;
- }
+ return detected;
+}
- return detected;
+- (BOOL)detectStyle:(NSRange)range {
+ if(range.length >= 1) {
+ return [self detectStyleInAttributedString:_input->textView.textStorage range:range];
} else {
return [OccurenceUtils detect:NSBackgroundColorAttributeName
withInput:_input
diff --git a/ios/styles/ItalicStyle.mm b/ios/styles/ItalicStyle.mm
index 82ddf7072..7110d7039 100644
--- a/ios/styles/ItalicStyle.mm
+++ b/ios/styles/ItalicStyle.mm
@@ -31,22 +31,21 @@ - (void)applyStyle:(NSRange)range {
}
}
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
+- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ [attributedString 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 setItalic];
+ [attributedString addAttribute:NSFontAttributeName value:newFont range:range];
+ }
+ }
+ ];
+}
+
+- (void)addAttributes:(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 setItalic];
- [_input->textView.textStorage addAttribute:NSFontAttributeName
- value:newFont
- range:range];
- }
- }];
+ [self addAttributesInAttributedString:_input->textView.textStorage range:range];
[_input->textView.textStorage endEditing];
}
@@ -61,22 +60,21 @@ - (void)addTypingAttributes {
}
}
+- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ [attributedString 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];
+ [attributedString addAttribute:NSFontAttributeName value:newFont range:range];
+ }
+ }
+ ];
+}
+
- (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 removeAttributesInAttributedString:_input->textView.textStorage range:range];
[_input->textView.textStorage endEditing];
}
@@ -96,14 +94,17 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
return font != nullptr && [font isItalic];
}
+- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ return [OccurenceUtils detect:NSFontAttributeName inString:attributedString inRange:range
+ withCondition: ^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value :range];
+ }
+ ];
+}
+
- (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];
- }];
+ if(range.length >= 1) {
+ return [self detectStyleInAttributedString: _input->textView.textStorage range:range];
} else {
return [OccurenceUtils detect:NSFontAttributeName
withInput:_input
diff --git a/ios/styles/LinkStyle.mm b/ios/styles/LinkStyle.mm
index c9da3fc54..06bea4769 100644
--- a/ios/styles/LinkStyle.mm
+++ b/ios/styles/LinkStyle.mm
@@ -37,36 +37,47 @@ - (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
// no-op for links
}
+- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ // no-op for links
+}
+
- (void)addTypingAttributes {
// no-op for links
}
+- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attr
+ range:(NSRange)range
+{
+ NSArray *links =
+ [OccurenceUtils allMultiple:@[ManualLinkAttributeName, AutomaticLinkAttributeName]
+ inString:attr
+ inRange:range
+ withCondition:^BOOL(id value, NSRange r) {
+ return [self styleCondition:value :r];
+ }];
+
+ for (StylePair *pair in links) {
+ NSRange fullRange = [self offline_fullLinkRangeInAttributedString:attr
+ atIndex:[pair.rangeValue rangeValue].location];
+
+ [attr removeAttribute:ManualLinkAttributeName range:fullRange];
+ [attr removeAttribute:AutomaticLinkAttributeName range:fullRange];
+
+ UIColor *primary = [_input->config primaryColor];
+ [attr addAttribute:NSForegroundColorAttributeName value:primary range:fullRange];
+ [attr addAttribute:NSUnderlineColorAttributeName value:primary range:fullRange];
+ [attr addAttribute:NSStrikethroughColorAttributeName value:primary range:fullRange];
+
+ if ([_input->config linkDecorationLine] == DecorationUnderline) {
+ [attr removeAttribute:NSUnderlineStyleAttributeName range:fullRange];
+ }
+ }
+}
+
// we have to make sure all links in the range get fully removed here
- (void)removeAttributes:(NSRange)range {
- NSArray *links = [self findAllOccurences:range];
[_input->textView.textStorage beginEditing];
- for (StylePair *pair in links) {
- NSRange linkRange =
- [self getFullLinkRangeAt:[pair.rangeValue rangeValue].location];
- [_input->textView.textStorage removeAttribute:ManualLinkAttributeName
- range:linkRange];
- [_input->textView.textStorage removeAttribute:AutomaticLinkAttributeName
- range:linkRange];
- [_input->textView.textStorage addAttribute:NSForegroundColorAttributeName
- value:[_input->config primaryColor]
- range:linkRange];
- [_input->textView.textStorage addAttribute:NSUnderlineColorAttributeName
- value:[_input->config primaryColor]
- range:linkRange];
- [_input->textView.textStorage addAttribute:NSStrikethroughColorAttributeName
- value:[_input->config primaryColor]
- range:linkRange];
- if ([_input->config linkDecorationLine] == DecorationUnderline) {
- [_input->textView.textStorage
- removeAttribute:NSUnderlineStyleAttributeName
- range:linkRange];
- }
- }
+ [self removeAttributesInAttributedString: _input->textView.textStorage range:range];
[_input->textView.textStorage endEditing];
// adjust typing attributes as well
@@ -126,6 +137,21 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
return linkValue != nullptr;
}
+- (BOOL)detectStyleInAttributedString:(NSAttributedString *)attrString
+ range:(NSRange)range
+{
+ BOOL onlyLinks =
+ [OccurenceUtils detectMultiple:@[ManualLinkAttributeName, AutomaticLinkAttributeName]
+ inString:attrString
+ inRange:range
+ withCondition:^BOOL(id value, NSRange r) {
+ return [self styleCondition:value :r];
+ }];
+
+ if (!onlyLinks) return NO;
+ return [self offline_isSingleLinkIn:attrString range:range];
+}
+
- (BOOL)detectStyle:(NSRange)range {
if (range.length >= 1) {
BOOL onlyLinks = [OccurenceUtils
@@ -584,4 +610,80 @@ - (void)removeConnectedLinksIfNeeded:(NSString *)word range:(NSRange)wordRange {
}
}
+- (void)addLinkInAttributedString:(NSMutableAttributedString *)attr
+ range:(NSRange)range
+ text:(NSString *)text
+ url:(NSString *)url
+ manual:(BOOL) manual
+{
+ if (!text || !url) return;
+
+ NSDictionary *attrs = [self offline_linkAttributesForURL:url manual: manual];
+ [attr addAttributes:attrs range:range];
+}
+
+- (NSMutableDictionary *)offline_linkAttributesForURL:(NSString *)url manual:(BOOL)manual
+{
+ NSMutableDictionary *attrs =
+ [_input->defaultTypingAttributes mutableCopy];
+
+ attrs[NSForegroundColorAttributeName] = [_input->config linkColor];
+ attrs[NSUnderlineColorAttributeName] = [_input->config linkColor];
+ attrs[NSStrikethroughColorAttributeName] = [_input->config linkColor];
+
+ if ([_input->config linkDecorationLine] == DecorationUnderline) {
+ attrs[NSUnderlineStyleAttributeName] = @(NSUnderlineStyleSingle);
+ }
+ if(manual) {
+ attrs[ManualLinkAttributeName] = url;
+ } else {
+ attrs[AutomaticLinkAttributeName] = url;
+ }
+
+ return attrs;
+}
+
+
+- (NSRange)offline_fullLinkRangeInAttributedString:(NSAttributedString *)attr
+ atIndex:(NSUInteger)location
+{
+ NSRange fullManual = NSMakeRange(0,0);
+ NSRange fullAuto = NSMakeRange(0,0);
+
+ NSRange bounds = NSMakeRange(0, attr.length);
+
+ if (location >= attr.length && attr.length > 0) {
+ location = attr.length - 1;
+ }
+
+ NSString *manual = [attr attribute:ManualLinkAttributeName
+ atIndex:location
+ longestEffectiveRange:&fullManual
+ inRange:bounds];
+
+ NSString *autoUrl = [attr attribute:AutomaticLinkAttributeName
+ atIndex:location
+ longestEffectiveRange:&fullAuto
+ inRange:bounds];
+
+ if (manual != nil) return fullManual;
+ if (autoUrl != nil) return fullAuto;
+
+ return NSMakeRange(0, 0);
+}
+
+- (BOOL)offline_isSingleLinkIn:(NSAttributedString *)attr
+ range:(NSRange)range
+{
+ NSArray *pairs =
+ [OccurenceUtils allMultiple:@[ManualLinkAttributeName, AutomaticLinkAttributeName]
+ inString:attr
+ inRange:range
+ withCondition:^BOOL(id value, NSRange r) {
+ return [self styleCondition:value :r];
+ }];
+
+ return pairs.count == 1;
+}
+
@end
diff --git a/ios/styles/MediaAttachment.h b/ios/styles/MediaAttachment.h
new file mode 100644
index 000000000..6af926b99
--- /dev/null
+++ b/ios/styles/MediaAttachment.h
@@ -0,0 +1,23 @@
+#import
+
+@class MediaAttachment;
+
+@protocol MediaAttachmentDelegate
+- (void)mediaAttachmentDidUpdate:(MediaAttachment *)attachment;
+@end
+
+@interface MediaAttachment : NSTextAttachment
+
+@property (nonatomic, weak) id delegate;
+@property (nonatomic, strong) NSString *uri;
+@property (nonatomic, assign) CGFloat width;
+@property (nonatomic, assign) CGFloat height;
+
+- (instancetype)initWithURI:(NSString *)uri
+ width:(CGFloat)width
+ height:(CGFloat)height;
+
+- (void)loadAsync;
+- (void)notifyUpdate;
+
+@end
diff --git a/ios/styles/MediaAttachment.mm b/ios/styles/MediaAttachment.mm
new file mode 100644
index 000000000..124f678cb
--- /dev/null
+++ b/ios/styles/MediaAttachment.mm
@@ -0,0 +1,33 @@
+#import "MediaAttachment.h"
+
+@implementation MediaAttachment
+
+- (instancetype)initWithURI:(NSString *)uri
+ width:(CGFloat)width
+ height:(CGFloat)height
+{
+ self = [super init];
+ if (!self) return nil;
+
+ _uri = uri;
+ _width = width;
+ _height = height;
+
+ self.bounds = CGRectMake(0, 0, width, height);
+
+ return self;
+}
+
+- (void)loadAsync
+{
+ // no-op for base
+}
+
+- (void)notifyUpdate
+{
+ if ([self.delegate respondsToSelector:@selector(mediaAttachmentDidUpdate:)]) {
+ [self.delegate mediaAttachmentDidUpdate:self];
+ }
+}
+
+@end
diff --git a/ios/styles/MentionStyle.mm b/ios/styles/MentionStyle.mm
index a60b687fb..f50793522 100644
--- a/ios/styles/MentionStyle.mm
+++ b/ios/styles/MentionStyle.mm
@@ -41,10 +41,41 @@ - (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
// no-op for mentions
}
+- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ // no-op for mentions
+}
+
- (void)addTypingAttributes {
// no-op for mentions
}
+- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString
+ range:(NSRange)range
+{
+ NSArray *mentions =
+ [self findAllOccurences:range];
+
+ for (StylePair *pair in mentions) {
+ NSRange fullRange =
+ [self getFullMentionRangeInAttributedString:attributedString
+ atIndex:[pair.rangeValue rangeValue].location];
+
+ [attributedString removeAttribute:MentionAttributeName range:fullRange];
+
+ // restore normal coloring
+ UIColor *primary = [_input->config primaryColor];
+ [attributedString addAttribute:NSForegroundColorAttributeName value:primary range:fullRange];
+ [attributedString addAttribute:NSUnderlineColorAttributeName value:primary range:fullRange];
+ [attributedString addAttribute:NSStrikethroughColorAttributeName value:primary range:fullRange];
+ [attributedString removeAttribute:NSBackgroundColorAttributeName range:fullRange];
+
+ MentionStyleProps *props = [self stylePropsWithParams:pair.styleValue];
+ if (props.decorationLine == DecorationUnderline) {
+ [attributedString removeAttribute:NSUnderlineStyleAttributeName range:fullRange];
+ }
+ }
+}
+
// we have to make sure all mentions get removed properly
- (void)removeAttributes:(NSRange)range {
BOOL someMentionHadUnderline = NO;
@@ -141,6 +172,17 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
return params != nullptr;
}
+- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString
+ range:(NSRange)range
+{
+ return [OccurenceUtils detect:MentionAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id value, NSRange r) {
+ return [self styleCondition:value :r];
+ }];
+}
+
- (BOOL)detectStyle:(NSRange)range {
if (range.length >= 1) {
return [OccurenceUtils detect:MentionAttributeName
@@ -688,4 +730,59 @@ - (void)removeActiveMentionRange {
}
}
+- (NSRange)getFullMentionRangeInAttributedString:(NSMutableAttributedString *)attrString
+ atIndex:(NSUInteger)location
+{
+ NSRange full = NSMakeRange(0, 0);
+ NSRange bounds = NSMakeRange(0, attrString.length);
+
+ if (location >= attrString.length && attrString.length > 0) {
+ location = attrString.length - 1;
+ }
+
+ [attrString attribute:MentionAttributeName
+ atIndex:location
+ longestEffectiveRange:&full
+ inRange:bounds];
+
+ return full;
+}
+
+- (void)addMentionInAttributedString:(NSMutableAttributedString *)string
+ range:(NSRange)range
+ params:(MentionParams *)params
+{
+ if (!string || !params) return;
+
+ MentionStyleProps *props =
+ [_input->config mentionStylePropsForIndicator:params.indicator];
+
+ NSMutableDictionary *attrs =
+ [_input->textView.typingAttributes mutableCopy];
+
+ attrs[MentionAttributeName] = params;
+ attrs[NSForegroundColorAttributeName] = props.color;
+ attrs[NSUnderlineColorAttributeName] = props.color;
+ attrs[NSStrikethroughColorAttributeName] = props.color;
+ attrs[NSBackgroundColorAttributeName] =
+ [props.backgroundColor colorWithAlphaIfNotTransparent:0.4];
+
+ if (props.decorationLine == DecorationUnderline) {
+ attrs[NSUnderlineStyleAttributeName] = @(NSUnderlineStyleSingle);
+ } else {
+ [attrs removeObjectForKey:NSUnderlineStyleAttributeName];
+ }
+
+ NSString *mentionText = params.text ?: @"";
+ NSAttributedString *mention =
+ [[NSAttributedString alloc] initWithString:mentionText attributes:attrs];
+
+ if (range.length == 0) {
+ [string insertAttributedString:mention atIndex:range.location];
+ } else {
+ [string replaceCharactersInRange:range withAttributedString:mention];
+ }
+}
+
+
@end
diff --git a/ios/styles/OrderedListStyle.mm b/ios/styles/OrderedListStyle.mm
index d9c918efa..7ba1d3e14 100644
--- a/ios/styles/OrderedListStyle.mm
+++ b/ios/styles/OrderedListStyle.mm
@@ -40,6 +40,53 @@ - (void)applyStyle:(NSRange)range {
}
}
+- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString
+ range:(NSRange)range
+{
+ NSTextList *numberBullet = [[NSTextList alloc] initWithMarkerFormat:NSTextListMarkerDecimal options:0];
+ NSArray *paragraphs =
+ [ParagraphsUtils getSeparateParagraphsRangesInAttributedString:attributedString range:range];
+
+ NSInteger offset = 0;
+
+ for (NSValue *val in paragraphs) {
+ NSRange p = val.rangeValue;
+ NSRange fixedRange = NSMakeRange(p.location + offset, p.length);
+
+ // Fill empty lines with ZWSP so the paragraph style can apply to at least 1 char
+ if (fixedRange.length == 0 ||
+ (fixedRange.length == 1 &&
+ [[NSCharacterSet newlineCharacterSet] characterIsMember:
+ [attributedString.string characterAtIndex:fixedRange.location]])) {
+
+ [TextInsertionUtils insertTextInAttributedString:@"\u200B"
+ at:fixedRange.location
+ additionalAttributes:nil
+ attributedString:attributedString];
+
+ fixedRange = NSMakeRange(fixedRange.location, fixedRange.length + 1);
+ offset += 1;
+ }
+
+ [attributedString enumerateAttribute:NSParagraphStyleAttributeName
+ inRange:fixedRange
+ options:0
+ usingBlock:^(id _Nullable value, NSRange subRange, BOOL * _Nonnull stop)
+ {
+ NSMutableParagraphStyle *pStyle = value ? [(NSParagraphStyle *)value mutableCopy]
+ : [NSMutableParagraphStyle new];
+
+ pStyle.textLists = @[ numberBullet ];
+ pStyle.headIndent = [self getHeadIndent];
+ pStyle.firstLineHeadIndent = [self getHeadIndent];
+ NSMutableDictionary *typingAttrs = [_input->textView.typingAttributes mutableCopy];
+ typingAttrs[NSParagraphStyleAttributeName] = pStyle;
+ [attributedString addAttribute:NSParagraphStyleAttributeName value:pStyle range:subRange];
+ }];
+ }
+}
+
+
// we assume correct paragraph range is already given
- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
NSTextList *numberBullet =
@@ -128,33 +175,28 @@ - (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) {
+- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesInAttributedString:attributedString range:range];
+
+ 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];
- }];
+ [attributedString 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;
+ [attributedString addAttribute:NSParagraphStyleAttributeName value:pStyle range:range];
+ }
+ ];
}
+}
+- (void)removeAttributes:(NSRange)range {
+ [_input->textView.textStorage beginEditing];
+
+ [self removeAttributesInAttributedString:_input->textView.textStorage range:range];
+
[_input->textView.textStorage endEditing];
// also remove typing attributes
@@ -242,14 +284,17 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
NSTextListMarkerDecimal;
}
+- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ return [OccurenceUtils detect:NSParagraphStyleAttributeName inString:attributedString inRange:range
+ withCondition: ^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value :range];
+ }
+ ];
+}
+
- (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];
- }];
+ if(range.length >= 1) {
+ return [self detectStyleInAttributedString:_input->textView.textStorage range:range];
} else {
return [OccurenceUtils detect:NSParagraphStyleAttributeName
withInput:_input
diff --git a/ios/styles/StrikethroughStyle.mm b/ios/styles/StrikethroughStyle.mm
index 9cf96d34a..7060d4cbf 100644
--- a/ios/styles/StrikethroughStyle.mm
+++ b/ios/styles/StrikethroughStyle.mm
@@ -30,10 +30,12 @@ - (void)applyStyle:(NSRange)range {
}
}
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
- [_input->textView.textStorage addAttribute:NSStrikethroughStyleAttributeName
- value:@(NSUnderlineStyleSingle)
- range:range];
+- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ [attributedString addAttribute:NSStrikethroughStyleAttributeName value:@(NSUnderlineStyleSingle) range:range];
+}
+
+- (void)addAttributes:(NSRange)range {
+ [self addAttributesInAttributedString: _input->textView.textStorage range:range];
}
- (void)addTypingAttributes {
@@ -43,10 +45,12 @@ - (void)addTypingAttributes {
_input->textView.typingAttributes = newTypingAttrs;
}
+- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ [attributedString removeAttribute:NSStrikethroughStyleAttributeName range:range];
+}
+
- (void)removeAttributes:(NSRange)range {
- [_input->textView.textStorage
- removeAttribute:NSStrikethroughStyleAttributeName
- range:range];
+ [self removeAttributesInAttributedString: _input->textView.textStorage range:range];
}
- (void)removeTypingAttributes {
@@ -62,14 +66,17 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
[strikethroughStyle intValue] != NSUnderlineStyleNone;
}
+- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ return [OccurenceUtils detect:NSStrikethroughStyleAttributeName inString:attributedString inRange:range
+ withCondition: ^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value :range];
+ }
+ ];
+}
+
- (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];
- }];
+ if(range.length >= 1) {
+ return [self detectStyleInAttributedString:_input->textView.textStorage range:range];
} else {
return [OccurenceUtils detect:NSStrikethroughStyleAttributeName
withInput:_input
diff --git a/ios/styles/UnderlineStyle.mm b/ios/styles/UnderlineStyle.mm
index 98050475d..427564f6d 100644
--- a/ios/styles/UnderlineStyle.mm
+++ b/ios/styles/UnderlineStyle.mm
@@ -30,10 +30,12 @@ - (void)applyStyle:(NSRange)range {
}
}
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
- [_input->textView.textStorage addAttribute:NSUnderlineStyleAttributeName
- value:@(NSUnderlineStyleSingle)
- range:range];
+- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ [attributedString addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:range];
+}
+
+- (void)addAttributes:(NSRange)range {
+ [self addAttributesInAttributedString: _input->textView.textStorage range:range];
}
- (void)addTypingAttributes {
@@ -43,9 +45,12 @@ - (void)addTypingAttributes {
_input->textView.typingAttributes = newTypingAttrs;
}
+- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ [attributedString removeAttribute:NSUnderlineStyleAttributeName range:range];
+}
+
- (void)removeAttributes:(NSRange)range {
- [_input->textView.textStorage removeAttribute:NSUnderlineStyleAttributeName
- range:range];
+ [self removeAttributesInAttributedString: _input->textView.textStorage range:range];
}
- (void)removeTypingAttributes {
@@ -99,14 +104,17 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
![self underlinedMentionConflictsInRange:range];
}
+- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ return [OccurenceUtils detect:NSStrikethroughStyleAttributeName inString:attributedString inRange:range
+ withCondition: ^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value :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];
- }];
+ if(range.length >= 1) {
+ return [self detectStyleInAttributedString:_input->textView.textStorage range:range];
} else {
return [OccurenceUtils detect:NSUnderlineStyleAttributeName
withInput:_input
diff --git a/ios/styles/UnorderedListStyle.mm b/ios/styles/UnorderedListStyle.mm
index 9fb2f7b24..468c43b8d 100644
--- a/ios/styles/UnorderedListStyle.mm
+++ b/ios/styles/UnorderedListStyle.mm
@@ -40,6 +40,41 @@ - (void)applyStyle:(NSRange)range {
}
}
+- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ NSTextList *bullet = [[NSTextList alloc] initWithMarkerFormat:NSTextListMarkerDisc options:0];
+ NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesInAttributedString:attributedString range:range];
+ // if we fill empty lines with zero width spaces, we need to offset later ranges
+ NSInteger offset = 0;
+
+ 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: [attributedString.string characterAtIndex:fixedRange.location]])
+ ) {
+ [TextInsertionUtils insertTextInAttributedString:@"\u200B" at:fixedRange.location additionalAttributes:nullptr attributedString: attributedString];
+ fixedRange = NSMakeRange(fixedRange.location, fixedRange.length + 1);
+ offset += 1;
+ }
+
+ [attributedString enumerateAttribute:NSParagraphStyleAttributeName inRange:fixedRange options:0
+ usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
+ NSMutableParagraphStyle *pStyle = value ? [(NSParagraphStyle *)value mutableCopy]
+ : [NSMutableParagraphStyle new];
+ pStyle.textLists = @[bullet];
+ pStyle.headIndent = [self getHeadIndent];
+ pStyle.firstLineHeadIndent = [self getHeadIndent];
+ NSMutableDictionary *typingAttrs = [_input->textView.typingAttributes mutableCopy];
+ typingAttrs[NSParagraphStyleAttributeName] = pStyle;
+ [attributedString addAttribute:NSParagraphStyleAttributeName value:pStyle range:range];
+ }
+ ];
+ }
+}
+
// we assume correct paragraph range is already given
- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
NSTextList *bullet =
@@ -127,33 +162,26 @@ - (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) {
+- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesInAttributedString:attributedString range:range];
+
+ 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];
- }];
+ [attributedString 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;
+ [attributedString addAttribute:NSParagraphStyleAttributeName value:pStyle range:range];
+ }
+ ];
}
+}
+- (void)removeAttributes:(NSRange)range {
+ [_input->textView.textStorage beginEditing];
+ [self addAttributesInAttributedString:_input->textView.textStorage range:range];
[_input->textView.textStorage endEditing];
// also remove typing attributes
@@ -240,14 +268,17 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
paragraph.textLists.firstObject.markerFormat == NSTextListMarkerDisc;
}
+- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+ return [OccurenceUtils detect:NSParagraphStyleAttributeName inString:attributedString inRange:range
+ withCondition: ^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value :range];
+ }
+ ];
+}
+
- (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];
- }];
+ if(range.length >= 1) {
+ return [self detectStyleInAttributedString:_input->textView.textStorage range:range];
} else {
return [OccurenceUtils detect:NSParagraphStyleAttributeName
withInput:_input
diff --git a/ios/utils/BaseStyleProtocol.h b/ios/utils/BaseStyleProtocol.h
index 5e08e558a..ca78b1b33 100644
--- a/ios/utils/BaseStyleProtocol.h
+++ b/ios/utils/BaseStyleProtocol.h
@@ -7,7 +7,10 @@
+ (BOOL)isParagraphStyle;
- (instancetype _Nonnull)initWithInput:(id _Nonnull)input;
- (void)applyStyle:(NSRange)range;
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr;
+- (void)addAttributes:(NSRange)range;
+- (void)addAttributesInAttributedString:(NSMutableAttributedString * _Nonnull)attributedString range:(NSRange)range;
+- (void)removeAttributesInAttributedString:(NSMutableAttributedString * _Nonnull)attributedString range:(NSRange)range;
+- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString * _Nonnull)attributedString range:(NSRange)range;
- (void)removeAttributes:(NSRange)range;
- (void)addTypingAttributes;
- (void)removeTypingAttributes;
diff --git a/ios/utils/OccurenceUtils.h b/ios/utils/OccurenceUtils.h
index 83cf12a65..831f5e168 100644
--- a/ios/utils/OccurenceUtils.h
+++ b/ios/utils/OccurenceUtils.h
@@ -1,47 +1,76 @@
#pragma once
-#import "EnrichedTextInputView.h"
+#import
#import "StylePair.h"
+#import "EnrichedTextInputView.h"
@interface OccurenceUtils : NSObject
+ (BOOL)detect:(NSAttributedStringKey _Nonnull)key
- withInput:(EnrichedTextInputView *_Nonnull)input
- inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
- NSRange range))condition;
+ inString:(NSAttributedString * _Nonnull)string
+ inRange:(NSRange)range
+ withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
+
++ (BOOL)detectMultiple:(NSArray * _Nonnull)keys
+ inString:(NSAttributedString * _Nonnull)string
+ inRange:(NSRange)range
+ withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
+
++ (BOOL)any:(NSAttributedStringKey _Nonnull)key
+ inString:(NSAttributedString * _Nonnull)string
+ inRange:(NSRange)range
+withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
+
++ (BOOL)anyMultiple:(NSArray * _Nonnull)keys
+ inString:(NSAttributedString * _Nonnull)string
+ inRange:(NSRange)range
+ withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
+
++ (NSArray * _Nonnull)all:(NSAttributedStringKey _Nonnull)key
+ inString:(NSAttributedString * _Nonnull)string
+ inRange:(NSRange)range
+ withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
+
++ (NSArray * _Nonnull)allMultiple:(NSArray * _Nonnull)keys
+ inString:(NSAttributedString * _Nonnull)string
+ inRange:(NSRange)range
+ withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
+ (BOOL)detect:(NSAttributedStringKey _Nonnull)key
- withInput:(EnrichedTextInputView *_Nonnull)input
- atIndex:(NSUInteger)index
- checkPrevious:(BOOL)check
- withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
- NSRange range))condition;
-+ (BOOL)detectMultiple:(NSArray *_Nonnull)keys
- withInput:(EnrichedTextInputView *_Nonnull)input
+ withInput:(EnrichedTextInputView * _Nonnull)input
+ inRange:(NSRange)range
+ withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
+
++ (BOOL)detect:(NSAttributedStringKey _Nonnull)key
+ withInput:(EnrichedTextInputView * _Nonnull)input
+ atIndex:(NSUInteger)index
+ checkPrevious:(BOOL)checkPrev
+ withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
+
++ (BOOL)detectMultiple:(NSArray * _Nonnull)keys
+ withInput:(EnrichedTextInputView * _Nonnull)input
inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
- NSRange range))condition;
+ withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
+
+ (BOOL)any:(NSAttributedStringKey _Nonnull)key
- withInput:(EnrichedTextInputView *_Nonnull)input
- inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
- NSRange range))condition;
-+ (BOOL)anyMultiple:(NSArray *_Nonnull)keys
- withInput:(EnrichedTextInputView *_Nonnull)input
+ withInput:(EnrichedTextInputView * _Nonnull)input
+ inRange:(NSRange)range
+withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
+
++ (BOOL)anyMultiple:(NSArray * _Nonnull)keys
+ withInput:(EnrichedTextInputView * _Nonnull)input
inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
- NSRange range))condition;
-+ (NSArray *_Nullable)all:(NSAttributedStringKey _Nonnull)key
- withInput:(EnrichedTextInputView *_Nonnull)input
- inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^
- _Nonnull)(id _Nullable value,
- NSRange range))condition;
-+ (NSArray *_Nullable)
- allMultiple:(NSArray *_Nonnull)keys
- withInput:(EnrichedTextInputView *_Nonnull)input
- inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
- NSRange range))condition;
-+ (NSArray *_Nonnull)getRangesWithout:(NSArray *_Nonnull)types
- withInput:(EnrichedTextInputView *_Nonnull)input
- inRange:(NSRange)range;
+ withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
+
++ (NSArray * _Nonnull)all:(NSAttributedStringKey _Nonnull)key
+ withInput:(EnrichedTextInputView * _Nonnull)input
+ inRange:(NSRange)range
+ withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
+
++ (NSArray * _Nonnull)allMultiple:(NSArray * _Nonnull)keys
+ withInput:(EnrichedTextInputView * _Nonnull)input
+ inRange:(NSRange)range
+ withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
+
++ (NSArray * _Nonnull)getRangesWithout:(NSArray * _Nonnull)types
+ withInput:(EnrichedTextInputView * _Nonnull)input
+ inRange:(NSRange)range;
+
@end
diff --git a/ios/utils/OccurenceUtils.mm b/ios/utils/OccurenceUtils.mm
index 43d58403c..4c7d38f8a 100644
--- a/ios/utils/OccurenceUtils.mm
+++ b/ios/utils/OccurenceUtils.mm
@@ -1,225 +1,332 @@
#import "OccurenceUtils.h"
+@interface OccurenceUtils ()
++ (void)enumerateAttributes:(NSArray *)keys
+ inString:(NSAttributedString *)string
+ inRange:(NSRange)range
+ withBlock:(void(NS_NOESCAPE ^)(NSAttributedStringKey key, id value, NSRange range, BOOL *stop))block;
+
++ (NSArray *)collectAttributes:(NSArray *)keys
+ inString:(NSAttributedString *)string
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition;
+@end
+
+
@implementation OccurenceUtils
-+ (BOOL)detect:(NSAttributedStringKey _Nonnull)key
- withInput:(EnrichedTextInputView *_Nonnull)input
- inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
- NSRange range))condition {
- __block NSInteger totalLength = 0;
- [input->textView.textStorage
- enumerateAttribute:key
++ (void)enumerateAttributes:(NSArray *)keys
+ inString:(NSAttributedString *)string
+ inRange:(NSRange)range
+ withBlock:(void(NS_NOESCAPE ^)(NSAttributedStringKey key,
+ id value,
+ NSRange range,
+ BOOL *stop))block
+{
+ __block BOOL outerStop = NO;
+
+ for (NSAttributedStringKey key in keys) {
+
+ [string enumerateAttribute:key
+ inRange:range
+ options:0
+ usingBlock:^(id value, NSRange subRange, BOOL *innerStop)
+ {
+ block(key, value, subRange, &outerStop);
+ if (outerStop) {
+ *innerStop = YES;
+ }
+ }];
+
+ if (outerStop) break;
+ }
+}
+
++ (NSArray *)collectAttributes:(NSArray *)keys
+ inString:(NSAttributedString *)string
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
+{
+ NSMutableArray *result = [NSMutableArray array];
+
+ [self enumerateAttributes:keys
+ inString:string
+ inRange:range
+ withBlock:^(NSAttributedStringKey key, id value, NSRange attrRange, BOOL *stop)
+ {
+ if (condition(value, attrRange)) {
+ StylePair *pair = [StylePair new];
+ pair.rangeValue = [NSValue valueWithRange:attrRange];
+ pair.styleValue = value;
+ [result addObject:pair];
+ }
+ }];
+
+ return result;
+}
+
+
+#pragma mark - ============================================================
+#pragma mark Public API (Attributed String Versions)
+#pragma mark - ============================================================
+
++ (BOOL)detect:(NSAttributedStringKey)key
+ inString:(NSAttributedString *)string
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
+{
+ __block NSInteger total = 0;
+
+ [self enumerateAttributes:@[key]
+ inString:string
+ inRange:range
+ withBlock:^(NSAttributedStringKey key, id value, NSRange r, BOOL *stop)
+ {
+ if (condition(value, r)) {
+ total += r.length;
+ }
+ }];
+
+ return total == range.length;
+}
+
++ (BOOL)detectMultiple:(NSArray *)keys
+ inString:(NSAttributedString *)string
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
+{
+ __block NSInteger total = 0;
+
+ [self enumerateAttributes:keys
+ inString:string
+ inRange:range
+ withBlock:^(NSAttributedStringKey key, id value, NSRange r, BOOL *stop)
+ {
+ if (condition(value, r)) {
+ total += r.length;
+ }
+ }];
+
+ return total == range.length;
+}
+
++ (BOOL)any:(NSAttributedStringKey)key
+ inString:(NSAttributedString *)string
+ inRange:(NSRange)range
+withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
+{
+ __block BOOL found = NO;
+
+ [self enumerateAttributes:@[key]
+ inString:string
+ inRange:range
+ withBlock:^(NSAttributedStringKey key, id value, NSRange r, BOOL *stop)
+ {
+ if (condition(value, r)) {
+ found = YES;
+ *stop = YES;
+ }
+ }];
+
+ return found;
+}
+
++ (BOOL)anyMultiple:(NSArray *)keys
+ inString:(NSAttributedString *)string
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
+{
+ __block BOOL found = NO;
+
+ [self enumerateAttributes:keys
+ inString:string
+ inRange:range
+ withBlock:^(NSAttributedStringKey key, id value, NSRange r, BOOL *stop)
+ {
+ if (condition(value, r)) {
+ found = YES;
+ *stop = YES;
+ }
+ }];
+
+ return found;
+}
+
++ (NSArray *)all:(NSAttributedStringKey)key
+ inString:(NSAttributedString *)string
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
+{
+ return [self collectAttributes:@[key]
+ inString:string
+ inRange:range
+ withCondition:condition];
+}
+
++ (NSArray *)allMultiple:(NSArray *)keys
+ inString:(NSAttributedString *)string
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
+{
+ return [self collectAttributes:keys
+ inString:string
+ inRange:range
+ withCondition:condition];
+}
+
+
+#pragma mark - ============================================================
+#pragma mark Public API (EnrichedTextInputView Versions)
+#pragma mark - ============================================================
+
+/// detects on a range using input->textView.textStorage
++ (BOOL)detect:(NSAttributedStringKey)key
+ withInput:(EnrichedTextInputView *)input
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
+{
+ return [self detect:key
+ inString:input->textView.textStorage
inRange:range
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- if (condition(value, range)) {
- totalLength += range.length;
- }
- }];
- return totalLength == range.length;
-}
-
-// checkPrevious flag is used for styles like lists or blockquotes
-// it means that first character of paragraph will be checked instead if the
-// detection is not in input's selected range and at the end of the input
-+ (BOOL)detect:(NSAttributedStringKey _Nonnull)key
- withInput:(EnrichedTextInputView *_Nonnull)input
- atIndex:(NSUInteger)index
- checkPrevious:(BOOL)checkPrev
- withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
- NSRange range))condition {
- NSRange detectionRange = NSMakeRange(index, 0);
- id attrValue;
- if (NSEqualRanges(input->textView.selectedRange, detectionRange)) {
- attrValue = input->textView.typingAttributes[key];
- } else if (index == input->textView.textStorage.string.length) {
- if (checkPrev) {
- NSRange paragraphRange = [input->textView.textStorage.string
- paragraphRangeForRange:detectionRange];
- if (paragraphRange.location == detectionRange.location) {
- return NO;
- } else {
- return [self detect:key
- withInput:input
- inRange:NSMakeRange(paragraphRange.location, 1)
- withCondition:condition];
- }
+ withCondition:condition];
+}
+
+/// detects at index (typing attributes logic preserved)
++ (BOOL)detect:(NSAttributedStringKey)key
+ withInput:(EnrichedTextInputView *)input
+ atIndex:(NSUInteger)index
+ checkPrevious:(BOOL)checkPrev
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
+{
+ NSRange detectionRange = NSMakeRange(index, 0);
+ id attrValue;
+
+ if (NSEqualRanges(input->textView.selectedRange, detectionRange)) {
+ attrValue = input->textView.typingAttributes[key];
+
+ } else if (index == input->textView.textStorage.string.length) {
+
+ if (checkPrev) {
+ NSRange paragraph = [input->textView.textStorage.string paragraphRangeForRange:detectionRange];
+ if (paragraph.location == detectionRange.location) {
+ return NO;
+ } else {
+ return [self detect:key
+ withInput:input
+ inRange:NSMakeRange(paragraph.location, 1)
+ withCondition:condition];
+ }
+ } else {
+ return NO;
+ }
+
} else {
- return NO;
+ NSRange eff;
+ attrValue = [input->textView.textStorage attribute:key
+ atIndex:index
+ effectiveRange:&eff];
}
- } else {
- NSRange attrRange = NSMakeRange(0, 0);
- attrValue = [input->textView.textStorage attribute:key
- atIndex:index
- effectiveRange:&attrRange];
- }
- return condition(attrValue, detectionRange);
+
+ return condition(attrValue, detectionRange);
}
-+ (BOOL)detectMultiple:(NSArray *_Nonnull)keys
- withInput:(EnrichedTextInputView *_Nonnull)input
++ (BOOL)detectMultiple:(NSArray *)keys
+ withInput:(EnrichedTextInputView *)input
inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
- NSRange range))condition {
- __block NSInteger totalLength = 0;
- for (NSString *key in keys) {
- [input->textView.textStorage
- enumerateAttribute:key
- inRange:range
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- if (condition(value, range)) {
- totalLength += range.length;
- }
- }];
- }
- return totalLength == range.length;
-}
-
-+ (BOOL)any:(NSAttributedStringKey _Nonnull)key
- withInput:(EnrichedTextInputView *_Nonnull)input
- inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
- NSRange range))condition {
- __block BOOL found = NO;
- [input->textView.textStorage
- enumerateAttribute:key
- inRange:range
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- if (condition(value, range)) {
- found = YES;
- *stop = YES;
- }
- }];
- return found;
-}
-
-+ (BOOL)anyMultiple:(NSArray *_Nonnull)keys
- withInput:(EnrichedTextInputView *_Nonnull)input
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
+{
+ return [self detectMultiple:keys
+ inString:input->textView.textStorage
+ inRange:range
+ withCondition:condition];
+}
+
++ (BOOL)any:(NSAttributedStringKey)key
+ withInput:(EnrichedTextInputView *)input
+ inRange:(NSRange)range
+withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
+{
+ return [self any:key
+ inString:input->textView.textStorage
+ inRange:range
+ withCondition:condition];
+}
+
++ (BOOL)anyMultiple:(NSArray *)keys
+ withInput:(EnrichedTextInputView *)input
inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
- NSRange range))condition {
- __block BOOL found = NO;
- for (NSString *key in keys) {
- [input->textView.textStorage
- enumerateAttribute:key
- inRange:range
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- if (condition(value, range)) {
- found = YES;
- *stop = YES;
- }
- }];
- if (found) {
- return YES;
- }
- }
- return NO;
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
+{
+ return [self anyMultiple:keys
+ inString:input->textView.textStorage
+ inRange:range
+ withCondition:condition];
}
-+ (NSArray *_Nullable)all:(NSAttributedStringKey _Nonnull)key
- withInput:(EnrichedTextInputView *_Nonnull)input
++ (NSArray *)all:(NSAttributedStringKey)key
+ withInput:(EnrichedTextInputView *)input
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
+{
+ return [self all:key
+ inString:input->textView.textStorage
+ inRange:range
+ withCondition:condition];
+}
+
++ (NSArray *)allMultiple:(NSArray *)keys
+ withInput:(EnrichedTextInputView *)input
inRange:(NSRange)range
- withCondition:
- (BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
- NSRange range))
- condition {
- __block NSMutableArray *occurences =
- [[NSMutableArray alloc] init];
- [input->textView.textStorage
- enumerateAttribute:key
- inRange:range
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- if (condition(value, range)) {
- StylePair *pair = [[StylePair alloc] init];
- pair.rangeValue = [NSValue valueWithRange:range];
- pair.styleValue = value;
- [occurences addObject:pair];
- }
- }];
- return occurences;
-}
-
-+ (NSArray *_Nullable)
- allMultiple:(NSArray *_Nonnull)keys
- withInput:(EnrichedTextInputView *_Nonnull)input
- inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
- NSRange range))condition {
- __block NSMutableArray *occurences =
- [[NSMutableArray alloc] init];
- for (NSString *key in keys) {
- [input->textView.textStorage
- enumerateAttribute:key
- inRange:range
- options:0
- usingBlock:^(id _Nullable value, NSRange range,
- BOOL *_Nonnull stop) {
- if (condition(value, range)) {
- StylePair *pair = [[StylePair alloc] init];
- pair.rangeValue = [NSValue valueWithRange:range];
- pair.styleValue = value;
- [occurences addObject:pair];
- }
- }];
- }
- return occurences;
-}
-
-+ (NSArray *_Nonnull)getRangesWithout:(NSArray *_Nonnull)types
- withInput:(EnrichedTextInputView *_Nonnull)input
- inRange:(NSRange)range {
- NSMutableArray *activeStyleObjects = [[NSMutableArray alloc] init];
- for (NSNumber *type in types) {
- id styleClass = input->stylesDict[type];
- [activeStyleObjects addObject:styleClass];
- }
-
- if (activeStyleObjects.count == 0) {
- return @[ [NSValue valueWithRange:range] ];
- }
-
- NSMutableArray *newRanges = [[NSMutableArray alloc] init];
- NSUInteger lastRangeLocation = range.location;
- NSUInteger endLocation = range.location + range.length;
-
- for (NSUInteger i = range.location; i < endLocation; i++) {
- NSRange currentRange = NSMakeRange(i, 1);
- BOOL forbiddenStyleFound = NO;
-
- for (id style in activeStyleObjects) {
- if ([style detectStyle:currentRange]) {
- forbiddenStyleFound = YES;
- break;
- }
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
+{
+ return [self allMultiple:keys
+ inString:input->textView.textStorage
+ inRange:range
+ withCondition:condition];
+}
+
++ (NSArray *)getRangesWithout:(NSArray *)types
+ withInput:(EnrichedTextInputView *)input
+ inRange:(NSRange)range
+{
+ NSMutableArray *activeStyles = [NSMutableArray array];
+
+ for (NSNumber *type in types) {
+ id style = input->stylesDict[type];
+ [activeStyles addObject:style];
+ }
+
+ if (activeStyles.count == 0) {
+ return @[[NSValue valueWithRange:range]];
}
- if (forbiddenStyleFound) {
- if (i > lastRangeLocation) {
- NSRange cleanRange =
- NSMakeRange(lastRangeLocation, i - lastRangeLocation);
- [newRanges addObject:[NSValue valueWithRange:cleanRange]];
- }
- lastRangeLocation = i + 1;
+ NSMutableArray *newRanges = [NSMutableArray array];
+ NSUInteger lastLocation = range.location;
+ NSUInteger end = range.location + range.length;
+
+ for (NSUInteger i = range.location; i < end; i++) {
+
+ BOOL forbidden = NO;
+ for (id style in activeStyles) {
+ if ([style detectStyle:NSMakeRange(i, 1)]) {
+ forbidden = YES;
+ break;
+ }
+ }
+
+ if (forbidden) {
+ if (i > lastLocation) {
+ [newRanges addObject:[NSValue valueWithRange:NSMakeRange(lastLocation, i - lastLocation)]];
+ }
+ lastLocation = i + 1;
+ }
}
- }
- if (lastRangeLocation < endLocation) {
- NSRange remainingRange =
- NSMakeRange(lastRangeLocation, endLocation - lastRangeLocation);
- [newRanges addObject:[NSValue valueWithRange:remainingRange]];
- }
+ if (lastLocation < end) {
+ [newRanges addObject:[NSValue valueWithRange:NSMakeRange(lastLocation, end - lastLocation)]];
+ }
- return newRanges;
+ return newRanges;
}
@end
diff --git a/ios/utils/ParagraphsUtils.h b/ios/utils/ParagraphsUtils.h
index a6a1cfabf..118f2d843 100644
--- a/ios/utils/ParagraphsUtils.h
+++ b/ios/utils/ParagraphsUtils.h
@@ -5,4 +5,6 @@
+ (NSArray *)getSeparateParagraphsRangesIn:(UITextView *)textView
range:(NSRange)range;
+ (NSArray *)getNonNewlineRangesIn:(UITextView *)textView range:(NSRange)range;
++ (NSArray *)getSeparateParagraphsRangesInAttributedString:(NSAttributedString *)attributedString range:(NSRange)range;
++ (NSArray *)getNonNewlineRangesInAttributedString:(NSAttributedString *)attributedString range:(NSRange)range;
@end
diff --git a/ios/utils/ParagraphsUtils.mm b/ios/utils/ParagraphsUtils.mm
index aa3c116a4..15830f28e 100644
--- a/ios/utils/ParagraphsUtils.mm
+++ b/ios/utils/ParagraphsUtils.mm
@@ -65,4 +65,54 @@ + (NSArray *)getNonNewlineRangesIn:(UITextView *)textView range:(NSRange)range {
return nonNewlineRanges;
}
++ (NSArray *)getSeparateParagraphsRangesInAttributedString:(NSAttributedString *)attributedString range:(NSRange)range {
+ // just in case, get full paragraphs range
+ NSRange fullRange = [attributedString.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 = fullRange.location; i < fullRange.location + fullRange.length; i++) {
+ unichar currentChar = [attributedString.string characterAtIndex:i];
+ if([[NSCharacterSet newlineCharacterSet] characterIsMember:currentChar]) {
+ NSRange paragraphRange = [attributedString.string paragraphRangeForRange:NSMakeRange(lastStart, i - lastStart)];
+ [results addObject: [NSValue valueWithRange:paragraphRange]];
+ lastStart = i+1;
+ }
+ }
+
+ if(lastStart < fullRange.location + fullRange.length) {
+ NSRange paragraphRange = [attributedString.string paragraphRangeForRange:NSMakeRange(lastStart, fullRange.location + fullRange.length - lastStart)];
+ [results addObject: [NSValue valueWithRange:paragraphRange]];
+ }
+
+ return results;
+}
+
++ (NSArray *)getNonNewlineRangesInAttributedString:(NSAttributedString *)attributedString range:(NSRange)range {
+ NSMutableArray *nonNewlineRanges = [[NSMutableArray alloc] init];
+ int lastRangeLocation = range.location;
+
+ for(int i = range.location; i < range.location + range.length; i++) {
+ unichar currentChar = [attributedString.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/StyleHeaders.h b/ios/utils/StyleHeaders.h
index 4c57321ab..60e3f52a6 100644
--- a/ios/utils/StyleHeaders.h
+++ b/ios/utils/StyleHeaders.h
@@ -30,8 +30,12 @@
- (void)manageLinkTypingAttributes;
- (void)handleAutomaticLinks:(NSString *)word inRange:(NSRange)wordRange;
- (void)handleManualLinks:(NSString *)word inRange:(NSRange)wordRange;
-- (BOOL)handleLeadingLinkReplacement:(NSRange)range
- replacementText:(NSString *)text;
+- (BOOL)handleLeadingLinkReplacement:(NSRange)range replacementText:(NSString *)text;
+- (void)addLinkInAttributedString:(NSMutableAttributedString *)attr
+ range:(NSRange)range
+ text:(NSString *)text
+ url:(NSString *)url
+ manual:(BOOL) manual;
@end
@interface MentionStyle : NSObject
@@ -48,6 +52,9 @@
- (MentionParams *)getMentionParamsAt:(NSUInteger)location;
- (NSRange)getFullMentionRangeAt:(NSUInteger)location;
- (NSValue *)getActiveMentionRange;
+- (void)addMentionInAttributedString:(NSMutableAttributedString *)string
+ range:(NSRange)range
+ params:(MentionParams *)params;
@end
@interface HeadingStyleBase : NSObject {
@@ -96,4 +103,7 @@
imageData:(ImageData *)imageData
withSelection:(BOOL)withSelection;
- (ImageData *)getImageDataAt:(NSUInteger)location;
+- (void)addImageInAttributedString:(NSMutableAttributedString *)attributedString
+ range:(NSRange)range
+ imageData:(ImageData *)imageData;
@end
diff --git a/ios/utils/TextInsertionUtils.h b/ios/utils/TextInsertionUtils.h
index 1012f3837..27f5b9054 100644
--- a/ios/utils/TextInsertionUtils.h
+++ b/ios/utils/TextInsertionUtils.h
@@ -1,17 +1,7 @@
#import
@interface TextInsertionUtils : NSObject
-+ (void)insertText:(NSString *)text
- at:(NSInteger)index
- additionalAttributes:
- (NSDictionary *)additionalAttrs
- input:(id)input
- withSelection:(BOOL)withSelection;
-+ (void)replaceText:(NSString *)text
- at:(NSRange)range
- additionalAttributes:
- (NSDictionary *)additionalAttrs
- input:(id)input
- withSelection:(BOOL)withSelection;
-;
++ (void)insertText:(NSString*)text at:(NSInteger)index additionalAttributes:(NSDictionary*)additionalAttrs input:(id)input withSelection:(BOOL)withSelection;
++ (void)replaceText:(NSString*)text at:(NSRange)range additionalAttributes:(NSDictionary*)additionalAttrs input:(id)input withSelection:(BOOL)withSelection;;
++ (void)insertTextInAttributedString:(NSString*)text at:(NSInteger)index additionalAttributes:(NSDictionary*)additionalAttrs attributedString:(NSMutableAttributedString *)attributedString;
@end
diff --git a/ios/utils/TextInsertionUtils.mm b/ios/utils/TextInsertionUtils.mm
index 6afa0b876..7a40a6023 100644
--- a/ios/utils/TextInsertionUtils.mm
+++ b/ios/utils/TextInsertionUtils.mm
@@ -63,4 +63,27 @@ + (void)replaceText:(NSString *)text
}
typedInput->recentlyChangedRange = NSMakeRange(range.location, text.length);
}
+
++ (void)insertTextInAttributedString:(NSString*)text
+ at:(NSInteger)index
+ additionalAttributes:(NSDictionary*)additionalAttrs
+ attributedString:(NSMutableAttributedString *)attributedString
+{
+ NSDictionary *baseAttributes = @{};
+ if (attributedString.length > 0 && index < attributedString.length) {
+ baseAttributes = [attributedString attributesAtIndex:index effectiveRange:nil];
+ }
+
+ NSMutableDictionary *attrs = [baseAttributes mutableCopy];
+ if (additionalAttrs) {
+ [attrs addEntriesFromDictionary:additionalAttrs];
+ }
+
+ NSAttributedString *newAttrString =
+ [[NSAttributedString alloc] initWithString:text attributes:attrs];
+
+ // 4. Insert into the parent attributed string
+ [attributedString insertAttributedString:newAttrString atIndex:index];
+}
+
@end
From ee68135d41bc447f396aaabb5db1a305f4dc9e94 Mon Sep 17 00:00:00 2001
From: IvanIhnatsiuk
Date: Thu, 11 Dec 2025 09:44:44 +0100
Subject: [PATCH 2/8] fix: unordered list styling persist
---
ios/EnrichedTextInputView.h | 29 +-
ios/EnrichedTextInputView.mm | 43 ++-
ios/inputParser/InputParser.mm | 331 +++++++++---------
ios/styles/BlockQuoteStyle.mm | 159 +++++----
ios/styles/BoldStyle.mm | 83 +++--
ios/styles/CodeBlockStyle.mm | 162 +++++----
ios/styles/HeadingStyleBase.mm | 102 +++---
ios/styles/ImageAttachment.h | 2 +-
ios/styles/ImageAttachment.mm | 39 ++-
ios/styles/ImageStyle.mm | 138 ++++----
ios/styles/InlineCodeStyle.mm | 124 ++++---
ios/styles/ItalicStyle.mm | 83 +++--
ios/styles/LinkStyle.mm | 189 +++++-----
ios/styles/MediaAttachment.h | 12 +-
ios/styles/MediaAttachment.mm | 32 +-
ios/styles/MentionStyle.mm | 159 +++++----
ios/styles/OrderedListStyle.mm | 188 +++++-----
ios/styles/StrikethroughStyle.mm | 44 ++-
ios/styles/UnderlineStyle.mm | 41 ++-
ios/styles/UnorderedListStyle.mm | 162 +++++----
ios/utils/BaseStyleProtocol.h | 12 +-
ios/utils/OccurenceUtils.h | 119 ++++---
ios/utils/OccurenceUtils.mm | 483 +++++++++++++-------------
ios/utils/ParagraphAttributesUtils.mm | 2 +-
ios/utils/ParagraphsUtils.h | 8 +-
ios/utils/ParagraphsUtils.mm | 69 ++--
ios/utils/StyleHeaders.h | 5 +-
ios/utils/TextInsertionUtils.h | 22 +-
ios/utils/TextInsertionUtils.mm | 34 +-
29 files changed, 1593 insertions(+), 1283 deletions(-)
diff --git a/ios/EnrichedTextInputView.h b/ios/EnrichedTextInputView.h
index 037eba7d6..74a4fd7a1 100644
--- a/ios/EnrichedTextInputView.h
+++ b/ios/EnrichedTextInputView.h
@@ -4,26 +4,37 @@
#import "InputParser.h"
#import "InputTextView.h"
#import "MediaAttachment.h"
+#import
+#import
#ifndef EnrichedTextInputViewNativeComponent_h
#define EnrichedTextInputViewNativeComponent_h
NS_ASSUME_NONNULL_BEGIN
-@interface EnrichedTextInputView : RCTViewComponentView {
- @public InputTextView *textView;
- @public NSRange recentlyChangedRange;
- @public InputConfig *config;
- @public InputParser *parser;
- @public NSMutableDictionary *defaultTypingAttributes;
- @public NSDictionary> *stylesDict;
+@interface EnrichedTextInputView
+ : RCTViewComponentView {
+@public
+ InputTextView *textView;
+@public
+ NSRange recentlyChangedRange;
+@public
+ InputConfig *config;
+@public
+ InputParser *parser;
+@public
+ NSMutableDictionary *defaultTypingAttributes;
+@public
+ NSDictionary> *stylesDict;
NSDictionary *> *conflictingStyles;
NSDictionary *> *blockingStyles;
@public
BOOL blockEmitting;
}
-@property (nonatomic, strong) NSDictionary *> *conflictingStyles;
-@property (nonatomic, strong) NSDictionary *> *blockingStyles;
+@property(nonatomic, strong)
+ NSDictionary *> *conflictingStyles;
+@property(nonatomic, strong)
+ NSDictionary *> *blockingStyles;
- (CGSize)measureSize:(CGFloat)maxWidth;
- (void)emitOnLinkDetectedEvent:(NSString *)text
url:(NSString *)url
diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm
index 012b9e05e..50104511b 100644
--- a/ios/EnrichedTextInputView.mm
+++ b/ios/EnrichedTextInputView.mm
@@ -219,31 +219,29 @@ - (void)setupPlaceholderLabel {
_placeholderLabel.hidden = YES;
}
-- (void)mediaAttachmentDidUpdate:(NSTextAttachment *)attachment
-{
+- (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;
- }
+ NSRange fullRange = NSMakeRange(0, storage.length);
+
+ __block NSRange foundRange = NSMakeRange(NSNotFound, 0);
- [storage edited:NSTextStorageEditedAttributes
+ [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];
+ changeInLength:0];
}
// MARK: - Props
@@ -1444,7 +1442,6 @@ - (void)_performRelayout {
});
}
-
- (void)didMoveToWindow {
[super didMoveToWindow];
[self layoutIfNeeded];
diff --git a/ios/inputParser/InputParser.mm b/ios/inputParser/InputParser.mm
index bee580edd..43b9d1f17 100644
--- a/ios/inputParser/InputParser.mm
+++ b/ios/inputParser/InputParser.mm
@@ -492,184 +492,189 @@ - (NSString *)tagContentForStyle:(NSNumber *)style
return @"";
}
-- (void)replaceWholeFromHtml:(NSString * _Nonnull)html {
- NSArray *processingResult = [self getTextAndStylesFromHtml:html];
- NSString *plainText = (NSString *)processingResult[0];
- NSArray *stylesInfo = (NSArray *)processingResult[1];
+- (void)replaceWholeFromHtml:(NSString *_Nonnull)html {
+ NSArray *processingResult = [self getTextAndStylesFromHtml:html];
+ NSString *plainText = (NSString *)processingResult[0];
+ NSArray *stylesInfo = (NSArray *)processingResult[1];
- NSMutableAttributedString *newAttr =
- [[NSMutableAttributedString alloc] initWithString:plainText
- attributes:_input->defaultTypingAttributes];
+ NSMutableAttributedString *newAttr = [[NSMutableAttributedString alloc]
+ initWithString:plainText
+ attributes:_input->defaultTypingAttributes];
- [self applyProcessedStyles:stylesInfo
- toAttributedString:newAttr
- offsetFromBeginning:0];
+ [self applyProcessedStyles:stylesInfo
+ toAttributedString:newAttr
+ offsetFromBeginning:0];
- NSTextStorage *storage = _input->textView.textStorage;
+ NSTextStorage *storage = _input->textView.textStorage;
- [storage setAttributedString:newAttr];
-
- _input->textView.typingAttributes = _input->defaultTypingAttributes;
- [_input anyTextMayHaveBeenModified];
+ [storage setAttributedString:newAttr];
+
+ _input->textView.typingAttributes = _input->defaultTypingAttributes;
+ [_input anyTextMayHaveBeenModified];
}
-- (BOOL)styleType:(NSNumber *)type existsInStyles:(NSArray *)styles atRange:(NSRange)r {
- for (NSArray *entry in styles) {
- NSNumber *otherType = entry[0];
- StylePair *pair = entry[1];
- if ([otherType isEqualToNumber:type] &&
- NSEqualRanges(pair.rangeValue.rangeValue, r)) {
- return YES;
- }
+- (BOOL)styleType:(NSNumber *)type
+ existsInStyles:(NSArray *)styles
+ atRange:(NSRange)r {
+ for (NSArray *entry in styles) {
+ NSNumber *otherType = entry[0];
+ StylePair *pair = entry[1];
+ if ([otherType isEqualToNumber:type] &&
+ NSEqualRanges(pair.rangeValue.rangeValue, r)) {
+ return YES;
}
- return NO;
+ }
+ return NO;
}
- (void)removeStyleType:(NSNumber *)type
- fromStyles:(NSMutableArray *)styles
- atRange:(NSRange)r
-{
- NSMutableArray *remove = [NSMutableArray array];
-
- for (NSArray *entry in styles) {
- NSNumber *otherType = entry[0];
- StylePair *pair = entry[1];
-
- if ([otherType isEqualToNumber:type] &&
- NSEqualRanges(pair.rangeValue.rangeValue, r)) {
- [remove addObject:entry];
- }
+ fromStyles:(NSMutableArray *)styles
+ atRange:(NSRange)r {
+ NSMutableArray *remove = [NSMutableArray array];
+
+ for (NSArray *entry in styles) {
+ NSNumber *otherType = entry[0];
+ StylePair *pair = entry[1];
+
+ if ([otherType isEqualToNumber:type] &&
+ NSEqualRanges(pair.rangeValue.rangeValue, r)) {
+ [remove addObject:entry];
}
+ }
- [styles removeObjectsInArray:remove];
+ [styles removeObjectsInArray:remove];
}
- (BOOL)shouldApplyStyle:(StyleType)styleType
- existingStyles:(NSArray *)styles
- at:(NSRange)range
-{
- NSArray *blocking = _input->blockingStyles[@(styleType)];
- for (NSNumber *b in blocking) {
- if ([self styleType:b existsInStyles:styles atRange:range]) {
- return NO;
- }
+ existingStyles:(NSArray *)styles
+ at:(NSRange)range {
+ NSArray *blocking = _input->blockingStyles[@(styleType)];
+ for (NSNumber *b in blocking) {
+ if ([self styleType:b existsInStyles:styles atRange:range]) {
+ return NO;
}
+ }
- NSArray *conflicting = _input->conflictingStyles[@(styleType)];
- for (NSNumber *c in conflicting) {
- [self removeStyleType:c fromStyles:styles atRange:range];
- }
+ NSArray *conflicting = _input->conflictingStyles[@(styleType)];
+ for (NSNumber *c in conflicting) {
+ [self removeStyleType:c fromStyles:styles atRange:range];
+ }
- return YES;
+ return YES;
}
- (void)applyProcessedStyles:(NSArray *)processedStyles
- toAttributedString:(NSMutableAttributedString *)attributedString
- offsetFromBeginning:(NSInteger)offset
-{
- NSArray *sorted =
- [processedStyles sortedArrayUsingComparator:^NSComparisonResult(NSArray *a, NSArray *b) {
+ toAttributedString:(NSMutableAttributedString *)attributedString
+ offsetFromBeginning:(NSInteger)offset {
+ NSArray *sorted = [processedStyles
+ sortedArrayUsingComparator:^NSComparisonResult(NSArray *a, NSArray *b) {
StylePair *pa = a[1];
StylePair *pb = b[1];
NSInteger la = offset + pa.rangeValue.rangeValue.location;
NSInteger lb = offset + pb.rangeValue.rangeValue.location;
- if (la > lb) return NSOrderedAscending;
- if (la < lb) return NSOrderedDescending;
+ if (la > lb)
+ return NSOrderedAscending;
+ if (la < lb)
+ return NSOrderedDescending;
return NSOrderedSame;
- }];
-
- [attributedString beginEditing];
-
- for (NSArray *arr in sorted) {
- NSNumber *styleType = arr[0];
- StylePair *stylePair = arr[1];
- id style = _input->stylesDict[styleType];
-
- NSRange r = NSMakeRange(
- offset + stylePair.rangeValue.rangeValue.location,
- stylePair.rangeValue.rangeValue.length
- );
- if ([styleType isEqualToNumber:@([LinkStyle getStyleType])]) {
- NSString *text = [attributedString.string substringWithRange:r];
- NSString *url = stylePair.styleValue;
- BOOL isManual = [text isEqualToString: url];
- [(LinkStyle *)style addLinkInAttributedString:attributedString range:r text:text url:url manual:isManual];
-
- } else if ([styleType isEqualToNumber:@([MentionStyle getStyleType])]) {
- [(MentionStyle *)style addMentionInAttributedString:attributedString
- range:r
- params:stylePair.styleValue];
- } else if ([styleType isEqualToNumber:@([ImageStyle getStyleType])]) {
- [(ImageStyle *)style addImageInAttributedString:attributedString
- range:r
- imageData:stylePair.styleValue];
- } else {
- [style addAttributesInAttributedString:attributedString range:r];
- }
+ }];
+
+ [attributedString beginEditing];
+
+ for (NSArray *arr in sorted) {
+ NSNumber *styleType = arr[0];
+ StylePair *stylePair = arr[1];
+ id style = _input->stylesDict[styleType];
+
+ NSRange r = NSMakeRange(offset + stylePair.rangeValue.rangeValue.location,
+ stylePair.rangeValue.rangeValue.length);
+ if ([styleType isEqualToNumber:@([LinkStyle getStyleType])]) {
+ NSString *text = [attributedString.string substringWithRange:r];
+ NSString *url = stylePair.styleValue;
+ BOOL isManual = [text isEqualToString:url];
+ [(LinkStyle *)style addLinkInAttributedString:attributedString
+ range:r
+ text:text
+ url:url
+ manual:isManual];
+
+ } else if ([styleType isEqualToNumber:@([MentionStyle getStyleType])]) {
+ [(MentionStyle *)style addMentionInAttributedString:attributedString
+ range:r
+ params:stylePair.styleValue];
+ } else if ([styleType isEqualToNumber:@([ImageStyle getStyleType])]) {
+ [(ImageStyle *)style addImageInAttributedString:attributedString
+ range:r
+ imageData:stylePair.styleValue];
+ } else {
+ [style addAttributesInAttributedString:attributedString range:r];
}
+ }
- [attributedString endEditing];
+ [attributedString endEditing];
}
-- (void)replaceFromHtml:(NSString * _Nonnull)html range:(NSRange)range
-{
- if (!html || range.length == 0) return;
- NSArray *processingResult = [self getTextAndStylesFromHtml:html];
- if (processingResult.count < 2) return;
-
- NSString *plainText = processingResult[0];
- NSArray *stylesInfo = processingResult[1];
-
- NSMutableAttributedString *inserted =
- [[NSMutableAttributedString alloc] initWithString:plainText
- attributes:_input->defaultTypingAttributes];
- [self applyProcessedStyles:stylesInfo
- toAttributedString:inserted
- offsetFromBeginning:0];
-
- NSTextStorage *storage = _input->textView.textStorage;
+- (void)replaceFromHtml:(NSString *_Nonnull)html range:(NSRange)range {
+ if (!html || range.length == 0)
+ return;
+ NSArray *processingResult = [self getTextAndStylesFromHtml:html];
+ if (processingResult.count < 2)
+ return;
- if (range.location > storage.length)
- range.location = storage.length;
+ NSString *plainText = processingResult[0];
+ NSArray *stylesInfo = processingResult[1];
- if (NSMaxRange(range) > storage.length)
- range.length = storage.length - range.location;
+ NSMutableAttributedString *inserted = [[NSMutableAttributedString alloc]
+ initWithString:plainText
+ attributes:_input->defaultTypingAttributes];
+ [self applyProcessedStyles:stylesInfo
+ toAttributedString:inserted
+ offsetFromBeginning:0];
- [storage beginEditing];
- [storage replaceCharactersInRange:range withAttributedString:inserted];
- [storage endEditing];
- _input->textView.selectedRange = NSMakeRange(range.location + inserted.length, 0);
+ NSTextStorage *storage = _input->textView.textStorage;
- _input->textView.typingAttributes = _input->defaultTypingAttributes;
+ if (range.location > storage.length)
+ range.location = storage.length;
- [_input anyTextMayHaveBeenModified];
-}
+ if (NSMaxRange(range) > storage.length)
+ range.length = storage.length - range.location;
+ [storage beginEditing];
+ [storage replaceCharactersInRange:range withAttributedString:inserted];
+ [storage endEditing];
+ _input->textView.selectedRange =
+ NSMakeRange(range.location + inserted.length, 0);
-- (void)insertFromHtml:(NSString * _Nonnull)html location:(NSInteger)location {
- NSArray *processingResult = [self getTextAndStylesFromHtml:html];
- NSString *plainText = processingResult[0];
- NSArray *stylesInfo = processingResult[1];
+ _input->textView.typingAttributes = _input->defaultTypingAttributes;
- // Base attributed segment for inserted text
- NSMutableAttributedString *inserted =
- [[NSMutableAttributedString alloc] initWithString:plainText
- attributes:_input->defaultTypingAttributes];
- [self applyProcessedStyles:stylesInfo
- toAttributedString:inserted
- offsetFromBeginning:0];
+ [_input anyTextMayHaveBeenModified];
+}
- if (location > _input->textView.textStorage.length) {
- location = _input->textView.textStorage.length;
- }
+- (void)insertFromHtml:(NSString *_Nonnull)html location:(NSInteger)location {
+ NSArray *processingResult = [self getTextAndStylesFromHtml:html];
+ NSString *plainText = processingResult[0];
+ NSArray *stylesInfo = processingResult[1];
+
+ // Base attributed segment for inserted text
+ NSMutableAttributedString *inserted = [[NSMutableAttributedString alloc]
+ initWithString:plainText
+ attributes:_input->defaultTypingAttributes];
+ [self applyProcessedStyles:stylesInfo
+ toAttributedString:inserted
+ offsetFromBeginning:0];
+
+ if (location > _input->textView.textStorage.length) {
+ location = _input->textView.textStorage.length;
+ }
- [_input->textView.textStorage beginEditing];
- [_input->textView.textStorage insertAttributedString:inserted atIndex:location];
- [_input->textView.textStorage endEditing];
+ [_input->textView.textStorage beginEditing];
+ [_input->textView.textStorage insertAttributedString:inserted
+ atIndex:location];
+ [_input->textView.textStorage endEditing];
- _input->textView.selectedRange = NSMakeRange(location + inserted.length, 0);
+ _input->textView.selectedRange = NSMakeRange(location + inserted.length, 0);
}
- (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html {
@@ -848,41 +853,39 @@ - (void)finalizeTagEntry:(NSMutableString *)tagName
}
- (void)sanitizeStyles:(NSMutableArray *)styles {
- NSMutableArray *toRemove = [NSMutableArray array];
- for (NSArray *entry in [styles copy]) {
- NSNumber *styleType = entry[0];
- StylePair *pair = entry[1];
- NSRange r = pair.rangeValue.rangeValue;
-
- BOOL shouldRemove = NO;
- NSArray *blocking = _input.blockingStyles[styleType];
- for (NSNumber *bType in blocking) {
- if ([self styleType:bType existsInStyles:styles atRange:r]) {
- shouldRemove = YES;
- break;
- }
- }
+ NSMutableArray *toRemove = [NSMutableArray array];
+ for (NSArray *entry in [styles copy]) {
+ NSNumber *styleType = entry[0];
+ StylePair *pair = entry[1];
+ NSRange r = pair.rangeValue.rangeValue;
+
+ BOOL shouldRemove = NO;
+ NSArray *blocking = _input.blockingStyles[styleType];
+ for (NSNumber *bType in blocking) {
+ if ([self styleType:bType existsInStyles:styles atRange:r]) {
+ shouldRemove = YES;
+ break;
+ }
+ }
- if (shouldRemove) {
- [toRemove addObject:entry];
- continue;
- }
+ if (shouldRemove) {
+ [toRemove addObject:entry];
+ continue;
+ }
- NSArray *conflicting = _input.conflictingStyles[styleType];
- for (NSNumber *cType in conflicting) {
- [self removeStyleType:cType fromStyles:styles atRange:r];
- }
+ NSArray *conflicting = _input.conflictingStyles[styleType];
+ for (NSNumber *cType in conflicting) {
+ [self removeStyleType:cType fromStyles:styles atRange:r];
+ }
- if (shouldRemove) {
- [toRemove addObject:entry];
- }
+ if (shouldRemove) {
+ [toRemove addObject:entry];
}
+ }
- [styles removeObjectsInArray:toRemove];
+ [styles removeObjectsInArray:toRemove];
}
-
-
- (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml {
NSMutableString *plainText = [[NSMutableString alloc] initWithString:@""];
NSMutableDictionary *ongoingTags = [[NSMutableDictionary alloc] init];
@@ -1182,7 +1185,7 @@ - (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml {
[processedStyles addObject:styleArr];
}
[self sanitizeStyles:processedStyles];
- return @[plainText, processedStyles];
+ return @[ plainText, processedStyles ];
}
@end
diff --git a/ios/styles/BlockQuoteStyle.mm b/ios/styles/BlockQuoteStyle.mm
index a4a0c21e5..40d5cc13b 100644
--- a/ios/styles/BlockQuoteStyle.mm
+++ b/ios/styles/BlockQuoteStyle.mm
@@ -35,48 +35,68 @@ - (CGFloat)getHeadIndent {
- (void)applyStyle:(NSRange)range {
BOOL isStylePresent = [self detectStyle:range];
if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributes:range withTypingAttr:YES];
+ isStylePresent ? [self removeAttributes:range] : [self addAttributes:range];
} else {
isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
}
}
-- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesInAttributedString:attributedString range:range];
- // if we fill empty lines with zero width spaces, we need to offset later ranges
+- (void)addAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ NSArray *paragraphs = [ParagraphsUtils
+ getSeparateParagraphsRangesInAttributedString:attributedString
+ range:range];
+ // if we fill empty lines with zero width spaces, we need to offset later
+ // ranges
NSInteger offset = 0;
-
- 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: [attributedString.string characterAtIndex:pRange.location]])
- ) {
- [TextInsertionUtils insertTextInAttributedString:@"\u200B" at:pRange.location additionalAttributes:nullptr attributedString:attributedString];
+
+ 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:[attributedString.string
+ characterAtIndex:pRange.location]])) {
+ [TextInsertionUtils insertTextInAttributedString:@"\u200B"
+ at:pRange.location
+ additionalAttributes:nullptr
+ attributedString:attributedString];
pRange = NSMakeRange(pRange.location, pRange.length + 1);
offset += 1;
}
-
- [attributedString enumerateAttribute:NSParagraphStyleAttributeName inRange:pRange options:0
- usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
- NSMutableParagraphStyle *pStyle = value ? [(NSParagraphStyle *)value mutableCopy]
- : [NSMutableParagraphStyle new];
- pStyle.headIndent = [self getHeadIndent];
- pStyle.firstLineHeadIndent = [self getHeadIndent];
- NSMutableDictionary *typingAttrs = [_input->textView.typingAttributes mutableCopy];
- typingAttrs[NSParagraphStyleAttributeName] = pStyle;
- [attributedString addAttribute:NSParagraphStyleAttributeName value:pStyle range:range];
- }
- ];
+
+ [attributedString
+ enumerateAttribute:NSParagraphStyleAttributeName
+ inRange:pRange
+ options:0
+ usingBlock:^(id _Nullable value, NSRange range,
+ BOOL *_Nonnull stop) {
+ NSMutableParagraphStyle *pStyle =
+ value ? [(NSParagraphStyle *)value mutableCopy]
+ : [NSMutableParagraphStyle new];
+ pStyle.headIndent = [self getHeadIndent];
+ pStyle.firstLineHeadIndent = [self getHeadIndent];
+ NSMutableDictionary *typingAttrs =
+ [_input->textView.typingAttributes mutableCopy];
+ typingAttrs[NSParagraphStyleAttributeName] = pStyle;
+ [attributedString addAttribute:NSParagraphStyleAttributeName
+ value:pStyle
+ range:range];
+ }];
}
}
- (void)addAttributes:(NSRange)range {
- NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView range:range];
- // if we fill empty lines with zero width spaces, we need to offset later ranges
+ 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;
@@ -134,43 +154,52 @@ - (void)addAttributes:(NSRange)range {
}
// 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;
- }
+ 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];
+ [self addAttributes:_input->textView.selectedRange];
}
-- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- NSArray *paragraphs = [ParagraphsUtils getNonNewlineRangesInAttributedString:attributedString range:range];
-
- for(NSValue *value in paragraphs) {
+- (void)removeAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ NSArray *paragraphs =
+ [ParagraphsUtils getNonNewlineRangesInAttributedString:attributedString
+ range:range];
+
+ for (NSValue *value in paragraphs) {
NSRange pRange = [value rangeValue];
- [attributedString enumerateAttribute:NSParagraphStyleAttributeName inRange:pRange options:0
- usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
- NSMutableParagraphStyle *pStyle = value ? [(NSParagraphStyle *)value mutableCopy]
- : [NSMutableParagraphStyle new];
- pStyle.headIndent = 0;
- pStyle.firstLineHeadIndent = 0;
- [attributedString addAttribute:NSParagraphStyleAttributeName value:pStyle range:range];
- }
- ];
+ [attributedString
+ enumerateAttribute:NSParagraphStyleAttributeName
+ inRange:pRange
+ options:0
+ usingBlock:^(id _Nullable value, NSRange range,
+ BOOL *_Nonnull stop) {
+ NSMutableParagraphStyle *pStyle =
+ value ? [(NSParagraphStyle *)value mutableCopy]
+ : [NSMutableParagraphStyle new];
+ pStyle.headIndent = 0;
+ pStyle.firstLineHeadIndent = 0;
+ [attributedString addAttribute:NSParagraphStyleAttributeName
+ value:pStyle
+ range:range];
+ }];
}
}
- (void)removeAttributes:(NSRange)range {
- [self removeAttributesInAttributedString: _input->textView.textStorage range:range];
-
+ [self removeAttributesInAttributedString:_input->textView.textStorage
+ range:range];
+
// also remove typing attributes
NSMutableDictionary *typingAttrs =
[_input->textView.typingAttributes mutableCopy];
@@ -218,17 +247,21 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
pStyle.textLists.count == 0;
}
-- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- return [OccurenceUtils detect:NSParagraphStyleAttributeName inString:attributedString inRange:range
- withCondition: ^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value :range];
- }
- ];
+- (BOOL)detectStyleInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils detect:NSParagraphStyleAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
}
- (BOOL)detectStyle:(NSRange)range {
- if(range.length >= 1) {
- return [self detectStyleInAttributedString: _input->textView.textStorage range:range];
+ if (range.length >= 1) {
+ return [self detectStyleInAttributedString:_input->textView.textStorage
+ range:range];
} else {
return [OccurenceUtils detect:NSParagraphStyleAttributeName
withInput:_input
diff --git a/ios/styles/BoldStyle.mm b/ios/styles/BoldStyle.mm
index 73e544086..f52ccf7a1 100644
--- a/ios/styles/BoldStyle.mm
+++ b/ios/styles/BoldStyle.mm
@@ -24,32 +24,32 @@ - (instancetype)initWithInput:(id)input {
- (void)applyStyle:(NSRange)range {
BOOL isStylePresent = [self detectStyle:range];
if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributes:range withTypingAttr:YES];
+ isStylePresent ? [self removeAttributes:range] : [self addAttributes:range];
} else {
isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
}
}
- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attr
- range:(NSRange)range
-{
- [attr enumerateAttribute:NSFontAttributeName
- inRange:range
- options:0
- usingBlock:^(id value, NSRange subrange, BOOL *stop) {
-
- UIFont *font = (UIFont *)value;
- if (font != nil) {
- UIFont *newFont = [font setBold];
- [attr addAttribute:NSFontAttributeName value:newFont range:subrange];
- }
- }];
+ range:(NSRange)range {
+ [attr enumerateAttribute:NSFontAttributeName
+ inRange:range
+ options:0
+ usingBlock:^(id value, NSRange subrange, BOOL *stop) {
+ UIFont *font = (UIFont *)value;
+ if (font != nil) {
+ UIFont *newFont = [font setBold];
+ [attr addAttribute:NSFontAttributeName
+ value:newFont
+ range:subrange];
+ }
+ }];
}
- (void)addAttributes:(NSRange)range {
[_input->textView.textStorage beginEditing];
- [self addAttributesInAttributedString: _input->textView.textStorage range:range];
+ [self addAttributesInAttributedString:_input->textView.textStorage
+ range:range];
[_input->textView.textStorage endEditing];
}
@@ -64,23 +64,28 @@ - (void)addTypingAttributes {
}
}
-- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- [attributedString enumerateAttribute:NSFontAttributeName
- inRange:range
- options:0
- usingBlock:^(id value, NSRange subrange, BOOL *stop) {
-
- UIFont *font = (UIFont *)value;
- if (font != nil) {
- UIFont *newFont = [font removeBold];
- [attributedString addAttribute:NSFontAttributeName value:newFont range:subrange];
- }
- }];
+- (void)removeAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ [attributedString
+ enumerateAttribute:NSFontAttributeName
+ inRange:range
+ options:0
+ usingBlock:^(id value, NSRange subrange, BOOL *stop) {
+ UIFont *font = (UIFont *)value;
+ if (font != nil) {
+ UIFont *newFont = [font removeBold];
+ [attributedString addAttribute:NSFontAttributeName
+ value:newFont
+ range:subrange];
+ }
+ }];
}
- (void)removeAttributes:(NSRange)range {
[_input->textView.textStorage beginEditing];
- [self removeAttributesInAttributedString: _input->textView.textStorage range:range];
+ [self removeAttributesInAttributedString:_input->textView.textStorage
+ range:range];
[_input->textView.textStorage endEditing];
}
@@ -123,17 +128,21 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
![self boldHeadingConflictsInRange:range type:H3];
}
-- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- return [OccurenceUtils detect:NSFontAttributeName inString:attributedString inRange:range
- withCondition: ^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value :range];
- }
- ];
+- (BOOL)detectStyleInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils detect:NSFontAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
}
- (BOOL)detectStyle:(NSRange)range {
- if(range.length >= 1) {
- return [self detectStyleInAttributedString: _input->textView.textStorage range: range];
+ if (range.length >= 1) {
+ return [self detectStyleInAttributedString:_input->textView.textStorage
+ range:range];
} else {
return [OccurenceUtils detect:NSFontAttributeName
withInput:_input
diff --git a/ios/styles/CodeBlockStyle.mm b/ios/styles/CodeBlockStyle.mm
index d034ba8b8..ebb7369a4 100644
--- a/ios/styles/CodeBlockStyle.mm
+++ b/ios/styles/CodeBlockStyle.mm
@@ -29,49 +29,71 @@ - (instancetype)initWithInput:(id)input {
- (void)applyStyle:(NSRange)range {
BOOL isStylePresent = [self detectStyle:range];
if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributes:range withTypingAttr:YES];
+ isStylePresent ? [self removeAttributes:range] : [self addAttributes:range];
} else {
isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
}
}
-- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- NSTextList *codeBlockList = [[NSTextList alloc] initWithMarkerFormat:@"codeblock" options:0];
- NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesInAttributedString:attributedString range:range];
- // if we fill empty lines with zero width spaces, we need to offset later ranges
+- (void)addAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ NSTextList *codeBlockList =
+ [[NSTextList alloc] initWithMarkerFormat:@"codeblock" options:0];
+ NSArray *paragraphs = [ParagraphsUtils
+ getSeparateParagraphsRangesInAttributedString:attributedString
+ range:range];
+ // if we fill empty lines with zero width spaces, we need to offset later
+ // ranges
NSInteger offset = 0;
-
- 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: [attributedString.string characterAtIndex:pRange.location]])
- ) {
- [TextInsertionUtils insertTextInAttributedString:@"\u200B" at:pRange.location additionalAttributes:nullptr attributedString:attributedString];
+
+ 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:[attributedString.string
+ characterAtIndex:pRange.location]])) {
+ [TextInsertionUtils insertTextInAttributedString:@"\u200B"
+ at:pRange.location
+ additionalAttributes:nullptr
+ attributedString:attributedString];
pRange = NSMakeRange(pRange.location, pRange.length + 1);
offset += 1;
}
-
- [attributedString enumerateAttribute:NSParagraphStyleAttributeName inRange:pRange options:0
- usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
- NSMutableParagraphStyle *pStyle = value ? [(NSParagraphStyle *)value mutableCopy]
- : [NSMutableParagraphStyle new];
- NSMutableDictionary *typingAttrs = [_input->textView.typingAttributes mutableCopy];
- pStyle.textLists = @[codeBlockList];
- typingAttrs[NSParagraphStyleAttributeName] = pStyle;
- [attributedString addAttribute:NSParagraphStyleAttributeName value:pStyle range:range];
- }
- ];
+
+ [attributedString
+ enumerateAttribute:NSParagraphStyleAttributeName
+ inRange:pRange
+ options:0
+ usingBlock:^(id _Nullable value, NSRange range,
+ BOOL *_Nonnull stop) {
+ NSMutableParagraphStyle *pStyle =
+ value ? [(NSParagraphStyle *)value mutableCopy]
+ : [NSMutableParagraphStyle new];
+ NSMutableDictionary *typingAttrs =
+ [_input->textView.typingAttributes mutableCopy];
+ pStyle.textLists = @[ codeBlockList ];
+ typingAttrs[NSParagraphStyleAttributeName] = pStyle;
+ [attributedString addAttribute:NSParagraphStyleAttributeName
+ value:pStyle
+ range:range];
+ }];
}
}
- (void)addAttributes:(NSRange)range {
- 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
+ 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;
@@ -127,43 +149,51 @@ - (void)addAttributes:(NSRange)range {
}
// 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;
- }
+ 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];
+ [self addAttributes:_input->textView.selectedRange];
}
-- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- NSArray *paragraphs = [ParagraphsUtils getNonNewlineRangesInAttributedString:attributedString range:range];
-
- for(NSValue *value in paragraphs) {
+- (void)removeAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ NSArray *paragraphs =
+ [ParagraphsUtils getNonNewlineRangesInAttributedString:attributedString
+ range:range];
+
+ for (NSValue *value in paragraphs) {
NSRange pRange = [value rangeValue];
-
- [attributedString enumerateAttribute:NSParagraphStyleAttributeName inRange:pRange options:0
- usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
- NSMutableParagraphStyle *pStyle = [(NSParagraphStyle *)value mutableCopy];
-
- pStyle.textLists = @[];
- [attributedString addAttribute:NSParagraphStyleAttributeName value:pStyle range:range];
- }
- ];
+
+ [attributedString enumerateAttribute:NSParagraphStyleAttributeName
+ inRange:pRange
+ options:0
+ usingBlock:^(id _Nullable value, NSRange range,
+ BOOL *_Nonnull stop) {
+ NSMutableParagraphStyle *pStyle =
+ [(NSParagraphStyle *)value mutableCopy];
+
+ pStyle.textLists = @[];
+ [attributedString
+ addAttribute:NSParagraphStyleAttributeName
+ value:pStyle
+ range:range];
+ }];
}
}
-
- (void)removeAttributes:(NSRange)range {
[_input->textView.textStorage beginEditing];
- [self removeAttributesInAttributedString: _input->textView.textStorage range:range];
+ [self removeAttributesInAttributedString:_input->textView.textStorage
+ range:range];
[_input->textView.textStorage endEditing];
// also remove typing attributes
@@ -207,17 +237,21 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
isEqualToString:@"codeblock"];
}
-- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- return [OccurenceUtils detect:NSParagraphStyleAttributeName inString:attributedString inRange:range
- withCondition: ^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value :range];
- }
- ];
+- (BOOL)detectStyleInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils detect:NSParagraphStyleAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
}
- (BOOL)detectStyle:(NSRange)range {
- if(range.length >= 1) {
- return [self detectStyleInAttributedString:_input->textView.textStorage range:range];
+ if (range.length >= 1) {
+ return [self detectStyleInAttributedString:_input->textView.textStorage
+ range:range];
} else {
return [OccurenceUtils detect:NSParagraphStyleAttributeName
withInput:_input
diff --git a/ios/styles/HeadingStyleBase.mm b/ios/styles/HeadingStyleBase.mm
index 1101273b5..a5953b6ce 100644
--- a/ios/styles/HeadingStyleBase.mm
+++ b/ios/styles/HeadingStyleBase.mm
@@ -32,36 +32,43 @@ - (instancetype)initWithInput:(id)input {
- (void)applyStyle:(NSRange)range {
BOOL isStylePresent = [self detectStyle:range];
if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributes:range withTypingAttr:YES];
+ isStylePresent ? [self removeAttributes:range] : [self addAttributes:range];
} else {
isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
}
}
// the range will already be the proper full paragraph/s range
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
+- (void)addAttributes:(NSRange)range {
[[self typedInput]->textView.textStorage beginEditing];
- [self addAttributesInAttributedString: [self typedInput]->textView.textStorage range:range];
+ [self addAttributesInAttributedString:[self typedInput]->textView.textStorage
+ range:range];
[[self typedInput]->textView.textStorage endEditing];
-
+
// also toggle typing attributes
[self addTypingAttributes];
}
-- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- [attributedString 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 setSize:[self getHeadingFontSize]];
- if([self isHeadingBold]) {
- newFont = [newFont setBold];
- }
- [attributedString addAttribute:NSFontAttributeName value:newFont range:range];
- }
- }
- ];
+- (void)addAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ [attributedString
+ 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 setSize:[self getHeadingFontSize]];
+ if ([self isHeadingBold]) {
+ newFont = [newFont setBold];
+ }
+ [attributedString addAttribute:NSFontAttributeName
+ value:newFont
+ range:range];
+ }
+ }];
}
// will always be called on empty paragraphs so only typing attributes can be
@@ -82,19 +89,29 @@ - (void)addTypingAttributes {
}
}
-- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- NSRange paragraphRange = [attributedString.string paragraphRangeForRange:range];
- [attributedString enumerateAttribute:NSFontAttributeName inRange:paragraphRange options:0
- usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
- if([self styleCondition:value :range]) {
- UIFont *newFont = [(UIFont *)value setSize:[[[self typedInput]->config primaryFontSize] floatValue]];
- if([self isHeadingBold]) {
- newFont = [newFont removeBold];
- }
- [attributedString addAttribute:NSFontAttributeName value:newFont range:range];
- }
- }
- ];
+- (void)removeAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ NSRange paragraphRange =
+ [attributedString.string paragraphRangeForRange:range];
+ [attributedString
+ enumerateAttribute:NSFontAttributeName
+ inRange:paragraphRange
+ options:0
+ usingBlock:^(id _Nullable value, NSRange range,
+ BOOL *_Nonnull stop) {
+ if ([self styleCondition:value:range]) {
+ UIFont *newFont = [(UIFont *)value
+ setSize:[[[self typedInput]->config primaryFontSize]
+ floatValue]];
+ if ([self isHeadingBold]) {
+ newFont = [newFont removeBold];
+ }
+ [attributedString addAttribute:NSFontAttributeName
+ value:newFont
+ range:range];
+ }
+ }];
}
// we need to remove the style from the whole paragraph
@@ -153,17 +170,22 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
return font != nullptr && font.pointSize == [self getHeadingFontSize];
}
-- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- return [OccurenceUtils detect:NSFontAttributeName inString:attributedString inRange:range
- withCondition: ^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value :range];
- }
- ];
+- (BOOL)detectStyleInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils detect:NSFontAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
}
- (BOOL)detectStyle:(NSRange)range {
- if(range.length >= 1) {
- return [self detectStyleInAttributedString: [self typedInput]->textView.textStorage range:range];
+ if (range.length >= 1) {
+ return [self
+ detectStyleInAttributedString:[self typedInput]->textView.textStorage
+ range:range];
} else {
return [OccurenceUtils detect:NSFontAttributeName
withInput:[self typedInput]
@@ -227,7 +249,7 @@ - (void)handleImproperHeadings {
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];
+ [self addAttributes:paragraphRange];
}
}
}
diff --git a/ios/styles/ImageAttachment.h b/ios/styles/ImageAttachment.h
index 481199532..98681f490 100644
--- a/ios/styles/ImageAttachment.h
+++ b/ios/styles/ImageAttachment.h
@@ -1,5 +1,5 @@
-#import "MediaAttachment.h"
#import "ImageData.h"
+#import "MediaAttachment.h"
@interface ImageAttachment : MediaAttachment
diff --git a/ios/styles/ImageAttachment.mm b/ios/styles/ImageAttachment.mm
index f97e840cf..de6974e6b 100644
--- a/ios/styles/ImageAttachment.mm
+++ b/ios/styles/ImageAttachment.mm
@@ -2,32 +2,33 @@
@implementation ImageAttachment
-- (instancetype)initWithImageData:(ImageData *)data
-{
- self = [super initWithURI:data.uri width:data.width height:data.height];
- if (!self) return nil;
+- (instancetype)initWithImageData:(ImageData *)data {
+ self = [super initWithURI:data.uri width:data.width height:data.height];
+ if (!self)
+ return nil;
- _imageData = data;
+ _imageData = data;
- [self loadAsync];
- return self;
+ [self loadAsync];
+ return self;
}
-- (void)loadAsync
-{
- NSURL *url = [NSURL URLWithString:self.uri];
- if (!url) return;
+- (void)loadAsync {
+ NSURL *url = [NSURL URLWithString:self.uri];
+ if (!url)
+ return;
- dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
- NSData *bytes = [NSData dataWithContentsOfURL:url];
- UIImage *img = bytes ? [UIImage imageWithData:bytes] : nil;
- if (!img) return;
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
+ NSData *bytes = [NSData dataWithContentsOfURL:url];
+ UIImage *img = bytes ? [UIImage imageWithData:bytes] : nil;
+ if (!img)
+ return;
- dispatch_async(dispatch_get_main_queue(), ^{
- self.image = img;
- [self notifyUpdate];
- });
+ dispatch_async(dispatch_get_main_queue(), ^{
+ self.image = img;
+ [self notifyUpdate];
});
+ });
}
@end
diff --git a/ios/styles/ImageStyle.mm b/ios/styles/ImageStyle.mm
index 8bae49244..a2aa24466 100644
--- a/ios/styles/ImageStyle.mm
+++ b/ios/styles/ImageStyle.mm
@@ -1,8 +1,8 @@
#import "EnrichedTextInputView.h"
+#import "ImageAttachment.h"
#import "OccurenceUtils.h"
#import "StyleHeaders.h"
#import "TextInsertionUtils.h"
-#import "ImageAttachment.h"
// custom NSAttributedStringKey to differentiate the image
static NSString *const ImageAttributeName = @"ImageAttributeName";
@@ -29,11 +29,13 @@ - (void)applyStyle:(NSRange)range {
// no-op for image
}
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
+- (void)addAttributes:(NSRange)range {
// no-op for image
}
-- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+- (void)addAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
// no-op for image
}
@@ -41,14 +43,17 @@ - (void)addTypingAttributes {
// no-op for image
}
-- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+- (void)removeAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
[attributedString removeAttribute:ImageAttributeName range:range];
[attributedString removeAttribute:NSAttachmentAttributeName range:range];
}
- (void)removeAttributes:(NSRange)range {
[_input->textView.textStorage beginEditing];
- [self removeAttributesInAttributedString: _input->textView.textStorage range:range];
+ [self removeAttributesInAttributedString:_input->textView.textStorage
+ range:range];
[_input->textView.textStorage endEditing];
}
@@ -73,17 +78,21 @@ - (BOOL)anyOccurence:(NSRange)range {
}];
}
-- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- return [OccurenceUtils detect:ImageAttributeName inString:attributedString inRange:range
- withCondition:^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value :range];
- }
- ];
+- (BOOL)detectStyleInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils detect:ImageAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
}
- (BOOL)detectStyle:(NSRange)range {
if (range.length >= 1) {
- return [self detectStyleInAttributedString: _input->textView.textStorage range:range];
+ return [self detectStyleInAttributedString:_input->textView.textStorage
+ range:range];
} else {
return [OccurenceUtils detect:ImageAttributeName
withInput:_input
@@ -125,74 +134,67 @@ - (ImageData *)getImageDataAt:(NSUInteger)location {
- (void)addImageAtRange:(NSRange)range
imageData:(ImageData *)imageData
- withSelection:(BOOL)withSelection
-{
- if (!imageData) return;
-
- ImageAttachment *attachment =
- [[ImageAttachment alloc] initWithImageData:imageData];
- attachment.delegate = _input;
-
- NSDictionary *attrs = @{
- NSAttachmentAttributeName : attachment,
- ImageAttributeName : imageData
- };
-
- NSString *placeholderChar = @"\uFFFC";
-
- if (range.length == 0) {
- [TextInsertionUtils insertText:placeholderChar
- at:range.location
- additionalAttributes:attrs
- input:_input
- withSelection:withSelection];
- } else {
- [TextInsertionUtils replaceText:placeholderChar
- at:range
- additionalAttributes:attrs
- input:_input
- withSelection:withSelection];
- }
+ withSelection:(BOOL)withSelection {
+ if (!imageData)
+ return;
+
+ ImageAttachment *attachment =
+ [[ImageAttachment alloc] initWithImageData:imageData];
+ attachment.delegate = _input;
+
+ NSDictionary *attrs =
+ @{NSAttachmentAttributeName : attachment, ImageAttributeName : imageData};
+
+ NSString *placeholderChar = @"\uFFFC";
+
+ if (range.length == 0) {
+ [TextInsertionUtils insertText:placeholderChar
+ at:range.location
+ additionalAttributes:attrs
+ input:_input
+ withSelection:withSelection];
+ } else {
+ [TextInsertionUtils replaceText:placeholderChar
+ at:range
+ additionalAttributes:attrs
+ input:_input
+ withSelection:withSelection];
+ }
}
- (void)addImage:(NSString *)uri width:(CGFloat)width height:(CGFloat)height {
- ImageData *data = [[ImageData alloc] init];
- data.uri = uri;
- data.width = width;
- data.height = height;
+ ImageData *data = [[ImageData alloc] init];
+ data.uri = uri;
+ data.width = width;
+ data.height = height;
- [self addImageAtRange:_input->textView.selectedRange
- imageData:data
- withSelection:YES];
+ [self addImageAtRange:_input->textView.selectedRange
+ imageData:data
+ withSelection:YES];
}
- (void)addImageInAttributedString:(NSMutableAttributedString *)string
range:(NSRange)range
- imageData:(ImageData *)imageData
-{
- if (!imageData) return;
+ imageData:(ImageData *)imageData {
+ if (!imageData)
+ return;
- ImageAttachment *attachment =
- [[ImageAttachment alloc] initWithImageData:imageData];
- attachment.delegate = _input;
+ ImageAttachment *attachment =
+ [[ImageAttachment alloc] initWithImageData:imageData];
+ attachment.delegate = _input;
- NSMutableDictionary *attrs =
- [_input->defaultTypingAttributes mutableCopy];
- attrs[NSAttachmentAttributeName] = attachment;
- attrs[ImageAttributeName] = imageData;
+ NSMutableDictionary *attrs = [_input->defaultTypingAttributes mutableCopy];
+ attrs[NSAttachmentAttributeName] = attachment;
+ attrs[ImageAttributeName] = imageData;
- NSAttributedString *imgString =
- [[NSAttributedString alloc] initWithString:@"\uFFFC"
- attributes:attrs];
+ NSAttributedString *imgString =
+ [[NSAttributedString alloc] initWithString:@"\uFFFC" attributes:attrs];
- if (range.length == 0) {
- [string insertAttributedString:imgString
- atIndex:range.location];
- } else {
- [string replaceCharactersInRange:range
- withAttributedString:imgString];
- }
+ if (range.length == 0) {
+ [string insertAttributedString:imgString atIndex:range.location];
+ } else {
+ [string replaceCharactersInRange:range withAttributedString:imgString];
+ }
}
-
@end
diff --git a/ios/styles/InlineCodeStyle.mm b/ios/styles/InlineCodeStyle.mm
index 0fd7f8e3a..c7fc71c63 100644
--- a/ios/styles/InlineCodeStyle.mm
+++ b/ios/styles/InlineCodeStyle.mm
@@ -26,27 +26,43 @@ - (instancetype)initWithInput:(id)input {
- (void)applyStyle:(NSRange)range {
BOOL isStylePresent = [self detectStyle:range];
if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributes:range withTypingAttr:YES];
+ isStylePresent ? [self removeAttributes:range] : [self addAttributes:range];
} else {
isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
}
}
-- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)currentRange {
- [attributedString addAttribute:NSBackgroundColorAttributeName value:[[_input->config inlineCodeBgColor] colorWithAlphaIfNotTransparent:0.4] range:currentRange];
- [attributedString addAttribute:NSForegroundColorAttributeName value:[_input->config inlineCodeFgColor] range:currentRange];
- [attributedString addAttribute:NSUnderlineColorAttributeName value:[_input->config inlineCodeFgColor] range:currentRange];
- [attributedString addAttribute:NSStrikethroughColorAttributeName value:[_input->config inlineCodeFgColor] range:currentRange];
- [attributedString enumerateAttribute:NSFontAttributeName inRange:currentRange options:0
- usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
- UIFont *font = (UIFont *)value;
- if(font != nullptr) {
- UIFont *newFont = [[[_input->config monospacedFont] withFontTraits:font] setSize:font.pointSize];
- [attributedString addAttribute:NSFontAttributeName value:newFont range:range];
- }
- }
- ];
+- (void)addAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)currentRange {
+ [attributedString addAttribute:NSBackgroundColorAttributeName
+ value:[[_input->config inlineCodeBgColor]
+ colorWithAlphaIfNotTransparent:0.4]
+ range:currentRange];
+ [attributedString addAttribute:NSForegroundColorAttributeName
+ value:[_input->config inlineCodeFgColor]
+ range:currentRange];
+ [attributedString addAttribute:NSUnderlineColorAttributeName
+ value:[_input->config inlineCodeFgColor]
+ range:currentRange];
+ [attributedString addAttribute:NSStrikethroughColorAttributeName
+ value:[_input->config inlineCodeFgColor]
+ range:currentRange];
+ [attributedString
+ enumerateAttribute:NSFontAttributeName
+ inRange:currentRange
+ options:0
+ usingBlock:^(id _Nullable value, NSRange range,
+ BOOL *_Nonnull stop) {
+ UIFont *font = (UIFont *)value;
+ if (font != nullptr) {
+ UIFont *newFont = [[[_input->config monospacedFont]
+ withFontTraits:font] setSize:font.pointSize];
+ [attributedString addAttribute:NSFontAttributeName
+ value:newFont
+ range:range];
+ }
+ }];
}
- (void)addAttributes:(NSRange)range {
@@ -57,7 +73,8 @@ - (void)addAttributes:(NSRange)range {
for (NSValue *value in nonNewlineRanges) {
NSRange currentRange = [value rangeValue];
[_input->textView.textStorage beginEditing];
- [self addAttributesInAttributedString: _input->textView.textStorage range: currentRange];
+ [self addAttributesInAttributedString:_input->textView.textStorage
+ range:currentRange];
[_input->textView.textStorage endEditing];
}
}
@@ -81,25 +98,40 @@ - (void)addTypingAttributes {
_input->textView.typingAttributes = newTypingAttrs;
}
-- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+- (void)removeAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
[attributedString removeAttribute:NSBackgroundColorAttributeName range:range];
- [attributedString addAttribute:NSForegroundColorAttributeName value:[_input->config primaryColor] range:range];
- [attributedString addAttribute:NSUnderlineColorAttributeName value:[_input->config primaryColor] range:range];
- [attributedString addAttribute:NSStrikethroughColorAttributeName value:[_input->config primaryColor] range:range];
- [attributedString 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];
- [attributedString addAttribute:NSFontAttributeName value:newFont range:range];
- }
- }
- ];
+ [attributedString addAttribute:NSForegroundColorAttributeName
+ value:[_input->config primaryColor]
+ range:range];
+ [attributedString addAttribute:NSUnderlineColorAttributeName
+ value:[_input->config primaryColor]
+ range:range];
+ [attributedString addAttribute:NSStrikethroughColorAttributeName
+ value:[_input->config primaryColor]
+ range:range];
+ [attributedString
+ 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];
+ [attributedString addAttribute:NSFontAttributeName
+ value:newFont
+ range:range];
+ }
+ }];
}
- (void)removeAttributes:(NSRange)range {
[_input->textView.textStorage beginEditing];
- [self removeAttributesInAttributedString: _input->textView.textStorage range:range];
+ [self removeAttributesInAttributedString:_input->textView.textStorage
+ range:range];
[_input->textView.textStorage endEditing];
}
@@ -148,21 +180,26 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
return bgColor != nullptr && mStyle != nullptr && ![mStyle detectStyle:range];
}
-- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+- (BOOL)detectStyleInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
// detect only in non-newline characters
- NSArray *nonNewlineRanges = [ParagraphsUtils getNonNewlineRangesIn:_input->textView range:range];
- if(nonNewlineRanges.count == 0) {
+ NSArray *nonNewlineRanges =
+ [ParagraphsUtils getNonNewlineRangesIn:_input->textView range:range];
+ if (nonNewlineRanges.count == 0) {
return NO;
}
-
+
BOOL detected = YES;
- for(NSValue *value in nonNewlineRanges) {
+ for (NSValue *value in nonNewlineRanges) {
NSRange currentRange = [value rangeValue];
- BOOL currentDetected = [OccurenceUtils detect:NSBackgroundColorAttributeName inString:attributedString inRange:currentRange
- withCondition: ^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value :range];
- }
- ];
+ BOOL currentDetected =
+ [OccurenceUtils detect:NSBackgroundColorAttributeName
+ inString:attributedString
+ inRange:currentRange
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
detected = detected && currentDetected;
}
@@ -170,8 +207,9 @@ - (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedStr
}
- (BOOL)detectStyle:(NSRange)range {
- if(range.length >= 1) {
- return [self detectStyleInAttributedString:_input->textView.textStorage range:range];
+ if (range.length >= 1) {
+ return [self detectStyleInAttributedString:_input->textView.textStorage
+ range:range];
} else {
return [OccurenceUtils detect:NSBackgroundColorAttributeName
withInput:_input
diff --git a/ios/styles/ItalicStyle.mm b/ios/styles/ItalicStyle.mm
index 7110d7039..f63e48c2c 100644
--- a/ios/styles/ItalicStyle.mm
+++ b/ios/styles/ItalicStyle.mm
@@ -24,28 +24,35 @@ - (instancetype)initWithInput:(id)input {
- (void)applyStyle:(NSRange)range {
BOOL isStylePresent = [self detectStyle:range];
if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributes:range withTypingAttr:YES];
+ isStylePresent ? [self removeAttributes:range] : [self addAttributes:range];
} else {
isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
}
}
-- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- [attributedString 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 setItalic];
- [attributedString addAttribute:NSFontAttributeName value:newFont range:range];
- }
- }
- ];
+- (void)addAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ [attributedString 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 setItalic];
+ [attributedString
+ addAttribute:NSFontAttributeName
+ value:newFont
+ range:range];
+ }
+ }];
}
- (void)addAttributes:(NSRange)range {
[_input->textView.textStorage beginEditing];
- [self addAttributesInAttributedString:_input->textView.textStorage range:range];
+ [self addAttributesInAttributedString:_input->textView.textStorage
+ range:range];
[_input->textView.textStorage endEditing];
}
@@ -60,21 +67,29 @@ - (void)addTypingAttributes {
}
}
-- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- [attributedString 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];
- [attributedString addAttribute:NSFontAttributeName value:newFont range:range];
- }
- }
- ];
+- (void)removeAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ [attributedString 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];
+ [attributedString
+ addAttribute:NSFontAttributeName
+ value:newFont
+ range:range];
+ }
+ }];
}
- (void)removeAttributes:(NSRange)range {
[_input->textView.textStorage beginEditing];
- [self removeAttributesInAttributedString:_input->textView.textStorage range:range];
+ [self removeAttributesInAttributedString:_input->textView.textStorage
+ range:range];
[_input->textView.textStorage endEditing];
}
@@ -94,17 +109,21 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
return font != nullptr && [font isItalic];
}
-- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- return [OccurenceUtils detect:NSFontAttributeName inString:attributedString inRange:range
- withCondition: ^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value :range];
- }
- ];
+- (BOOL)detectStyleInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils detect:NSFontAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
}
- (BOOL)detectStyle:(NSRange)range {
- if(range.length >= 1) {
- return [self detectStyleInAttributedString: _input->textView.textStorage range:range];
+ if (range.length >= 1) {
+ return [self detectStyleInAttributedString:_input->textView.textStorage
+ range:range];
} else {
return [OccurenceUtils detect:NSFontAttributeName
withInput:_input
diff --git a/ios/styles/LinkStyle.mm b/ios/styles/LinkStyle.mm
index 06bea4769..1d7891acc 100644
--- a/ios/styles/LinkStyle.mm
+++ b/ios/styles/LinkStyle.mm
@@ -33,11 +33,13 @@ - (void)applyStyle:(NSRange)range {
// no-op for links
}
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
+- (void)addAttributes:(NSRange)range {
// no-op for links
}
-- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+- (void)addAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
// no-op for links
}
@@ -46,38 +48,46 @@ - (void)addTypingAttributes {
}
- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attr
- range:(NSRange)range
-{
- NSArray *links =
- [OccurenceUtils allMultiple:@[ManualLinkAttributeName, AutomaticLinkAttributeName]
- inString:attr
- inRange:range
- withCondition:^BOOL(id value, NSRange r) {
- return [self styleCondition:value :r];
- }];
-
- for (StylePair *pair in links) {
- NSRange fullRange = [self offline_fullLinkRangeInAttributedString:attr
- atIndex:[pair.rangeValue rangeValue].location];
-
- [attr removeAttribute:ManualLinkAttributeName range:fullRange];
- [attr removeAttribute:AutomaticLinkAttributeName range:fullRange];
-
- UIColor *primary = [_input->config primaryColor];
- [attr addAttribute:NSForegroundColorAttributeName value:primary range:fullRange];
- [attr addAttribute:NSUnderlineColorAttributeName value:primary range:fullRange];
- [attr addAttribute:NSStrikethroughColorAttributeName value:primary range:fullRange];
-
- if ([_input->config linkDecorationLine] == DecorationUnderline) {
- [attr removeAttribute:NSUnderlineStyleAttributeName range:fullRange];
- }
+ range:(NSRange)range {
+ NSArray *links = [OccurenceUtils
+ allMultiple:@[ ManualLinkAttributeName, AutomaticLinkAttributeName ]
+ inString:attr
+ inRange:range
+ withCondition:^BOOL(id value, NSRange r) {
+ return [self styleCondition:value:r];
+ }];
+
+ for (StylePair *pair in links) {
+ NSRange fullRange = [self
+ offline_fullLinkRangeInAttributedString:attr
+ atIndex:[pair.rangeValue rangeValue]
+ .location];
+
+ [attr removeAttribute:ManualLinkAttributeName range:fullRange];
+ [attr removeAttribute:AutomaticLinkAttributeName range:fullRange];
+
+ UIColor *primary = [_input->config primaryColor];
+ [attr addAttribute:NSForegroundColorAttributeName
+ value:primary
+ range:fullRange];
+ [attr addAttribute:NSUnderlineColorAttributeName
+ value:primary
+ range:fullRange];
+ [attr addAttribute:NSStrikethroughColorAttributeName
+ value:primary
+ range:fullRange];
+
+ if ([_input->config linkDecorationLine] == DecorationUnderline) {
+ [attr removeAttribute:NSUnderlineStyleAttributeName range:fullRange];
}
+ }
}
// we have to make sure all links in the range get fully removed here
- (void)removeAttributes:(NSRange)range {
[_input->textView.textStorage beginEditing];
- [self removeAttributesInAttributedString: _input->textView.textStorage range:range];
+ [self removeAttributesInAttributedString:_input->textView.textStorage
+ range:range];
[_input->textView.textStorage endEditing];
// adjust typing attributes as well
@@ -138,18 +148,18 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
}
- (BOOL)detectStyleInAttributedString:(NSAttributedString *)attrString
- range:(NSRange)range
-{
- BOOL onlyLinks =
- [OccurenceUtils detectMultiple:@[ManualLinkAttributeName, AutomaticLinkAttributeName]
- inString:attrString
- inRange:range
- withCondition:^BOOL(id value, NSRange r) {
- return [self styleCondition:value :r];
- }];
-
- if (!onlyLinks) return NO;
- return [self offline_isSingleLinkIn:attrString range:range];
+ range:(NSRange)range {
+ BOOL onlyLinks = [OccurenceUtils
+ detectMultiple:@[ ManualLinkAttributeName, AutomaticLinkAttributeName ]
+ inString:attrString
+ inRange:range
+ withCondition:^BOOL(id value, NSRange r) {
+ return [self styleCondition:value:r];
+ }];
+
+ if (!onlyLinks)
+ return NO;
+ return [self offline_isSingleLinkIn:attrString range:range];
}
- (BOOL)detectStyle:(NSRange)range {
@@ -614,76 +624,73 @@ - (void)addLinkInAttributedString:(NSMutableAttributedString *)attr
range:(NSRange)range
text:(NSString *)text
url:(NSString *)url
- manual:(BOOL) manual
-{
- if (!text || !url) return;
+ manual:(BOOL)manual {
+ if (!text || !url)
+ return;
- NSDictionary *attrs = [self offline_linkAttributesForURL:url manual: manual];
- [attr addAttributes:attrs range:range];
+ NSDictionary *attrs = [self offline_linkAttributesForURL:url manual:manual];
+ [attr addAttributes:attrs range:range];
}
-- (NSMutableDictionary *)offline_linkAttributesForURL:(NSString *)url manual:(BOOL)manual
-{
- NSMutableDictionary *attrs =
- [_input->defaultTypingAttributes mutableCopy];
+- (NSMutableDictionary *)offline_linkAttributesForURL:(NSString *)url
+ manual:(BOOL)manual {
+ NSMutableDictionary *attrs = [_input->defaultTypingAttributes mutableCopy];
- attrs[NSForegroundColorAttributeName] = [_input->config linkColor];
- attrs[NSUnderlineColorAttributeName] = [_input->config linkColor];
- attrs[NSStrikethroughColorAttributeName] = [_input->config linkColor];
+ attrs[NSForegroundColorAttributeName] = [_input->config linkColor];
+ attrs[NSUnderlineColorAttributeName] = [_input->config linkColor];
+ attrs[NSStrikethroughColorAttributeName] = [_input->config linkColor];
- if ([_input->config linkDecorationLine] == DecorationUnderline) {
- attrs[NSUnderlineStyleAttributeName] = @(NSUnderlineStyleSingle);
- }
- if(manual) {
- attrs[ManualLinkAttributeName] = url;
- } else {
- attrs[AutomaticLinkAttributeName] = url;
- }
+ if ([_input->config linkDecorationLine] == DecorationUnderline) {
+ attrs[NSUnderlineStyleAttributeName] = @(NSUnderlineStyleSingle);
+ }
+ if (manual) {
+ attrs[ManualLinkAttributeName] = url;
+ } else {
+ attrs[AutomaticLinkAttributeName] = url;
+ }
- return attrs;
+ return attrs;
}
-
- (NSRange)offline_fullLinkRangeInAttributedString:(NSAttributedString *)attr
- atIndex:(NSUInteger)location
-{
- NSRange fullManual = NSMakeRange(0,0);
- NSRange fullAuto = NSMakeRange(0,0);
+ atIndex:(NSUInteger)location {
+ NSRange fullManual = NSMakeRange(0, 0);
+ NSRange fullAuto = NSMakeRange(0, 0);
- NSRange bounds = NSMakeRange(0, attr.length);
+ NSRange bounds = NSMakeRange(0, attr.length);
- if (location >= attr.length && attr.length > 0) {
- location = attr.length - 1;
- }
+ if (location >= attr.length && attr.length > 0) {
+ location = attr.length - 1;
+ }
- NSString *manual = [attr attribute:ManualLinkAttributeName
- atIndex:location
- longestEffectiveRange:&fullManual
- inRange:bounds];
+ NSString *manual = [attr attribute:ManualLinkAttributeName
+ atIndex:location
+ longestEffectiveRange:&fullManual
+ inRange:bounds];
- NSString *autoUrl = [attr attribute:AutomaticLinkAttributeName
- atIndex:location
+ NSString *autoUrl = [attr attribute:AutomaticLinkAttributeName
+ atIndex:location
longestEffectiveRange:&fullAuto
inRange:bounds];
- if (manual != nil) return fullManual;
- if (autoUrl != nil) return fullAuto;
+ if (manual != nil)
+ return fullManual;
+ if (autoUrl != nil)
+ return fullAuto;
- return NSMakeRange(0, 0);
+ return NSMakeRange(0, 0);
}
-- (BOOL)offline_isSingleLinkIn:(NSAttributedString *)attr
- range:(NSRange)range
-{
- NSArray *pairs =
- [OccurenceUtils allMultiple:@[ManualLinkAttributeName, AutomaticLinkAttributeName]
- inString:attr
- inRange:range
- withCondition:^BOOL(id value, NSRange r) {
- return [self styleCondition:value :r];
- }];
-
- return pairs.count == 1;
+- (BOOL)offline_isSingleLinkIn:(NSAttributedString *)attr range:(NSRange)range {
+ NSArray *pairs = [OccurenceUtils
+ allMultiple:@[ ManualLinkAttributeName, AutomaticLinkAttributeName ]
+ inString:attr
+ inRange:range
+ withCondition:^BOOL(id value, NSRange r) {
+ return [self styleCondition:value:r];
+ }];
+
+ return pairs.count == 1;
}
@end
diff --git a/ios/styles/MediaAttachment.h b/ios/styles/MediaAttachment.h
index 6af926b99..050a993f8 100644
--- a/ios/styles/MediaAttachment.h
+++ b/ios/styles/MediaAttachment.h
@@ -8,14 +8,14 @@
@interface MediaAttachment : NSTextAttachment
-@property (nonatomic, weak) id delegate;
-@property (nonatomic, strong) NSString *uri;
-@property (nonatomic, assign) CGFloat width;
-@property (nonatomic, assign) CGFloat height;
+@property(nonatomic, weak) id delegate;
+@property(nonatomic, strong) NSString *uri;
+@property(nonatomic, assign) CGFloat width;
+@property(nonatomic, assign) CGFloat height;
- (instancetype)initWithURI:(NSString *)uri
- width:(CGFloat)width
- height:(CGFloat)height;
+ width:(CGFloat)width
+ height:(CGFloat)height;
- (void)loadAsync;
- (void)notifyUpdate;
diff --git a/ios/styles/MediaAttachment.mm b/ios/styles/MediaAttachment.mm
index 124f678cb..7eed179e8 100644
--- a/ios/styles/MediaAttachment.mm
+++ b/ios/styles/MediaAttachment.mm
@@ -3,31 +3,29 @@
@implementation MediaAttachment
- (instancetype)initWithURI:(NSString *)uri
- width:(CGFloat)width
- height:(CGFloat)height
-{
- self = [super init];
- if (!self) return nil;
+ width:(CGFloat)width
+ height:(CGFloat)height {
+ self = [super init];
+ if (!self)
+ return nil;
- _uri = uri;
- _width = width;
- _height = height;
+ _uri = uri;
+ _width = width;
+ _height = height;
- self.bounds = CGRectMake(0, 0, width, height);
+ self.bounds = CGRectMake(0, 0, width, height);
- return self;
+ return self;
}
-- (void)loadAsync
-{
+- (void)loadAsync {
// no-op for base
}
-- (void)notifyUpdate
-{
- if ([self.delegate respondsToSelector:@selector(mediaAttachmentDidUpdate:)]) {
- [self.delegate mediaAttachmentDidUpdate:self];
- }
+- (void)notifyUpdate {
+ if ([self.delegate respondsToSelector:@selector(mediaAttachmentDidUpdate:)]) {
+ [self.delegate mediaAttachmentDidUpdate:self];
+ }
}
@end
diff --git a/ios/styles/MentionStyle.mm b/ios/styles/MentionStyle.mm
index f50793522..9b1c5d33f 100644
--- a/ios/styles/MentionStyle.mm
+++ b/ios/styles/MentionStyle.mm
@@ -37,11 +37,13 @@ - (void)applyStyle:(NSRange)range {
// no-op for mentions
}
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
+- (void)addAttributes:(NSRange)range {
// no-op for mentions
}
-- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+- (void)addAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
// no-op for mentions
}
@@ -49,31 +51,39 @@ - (void)addTypingAttributes {
// no-op for mentions
}
-- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString
- range:(NSRange)range
-{
- NSArray *mentions =
- [self findAllOccurences:range];
-
- for (StylePair *pair in mentions) {
- NSRange fullRange =
- [self getFullMentionRangeInAttributedString:attributedString
- atIndex:[pair.rangeValue rangeValue].location];
-
- [attributedString removeAttribute:MentionAttributeName range:fullRange];
-
- // restore normal coloring
- UIColor *primary = [_input->config primaryColor];
- [attributedString addAttribute:NSForegroundColorAttributeName value:primary range:fullRange];
- [attributedString addAttribute:NSUnderlineColorAttributeName value:primary range:fullRange];
- [attributedString addAttribute:NSStrikethroughColorAttributeName value:primary range:fullRange];
- [attributedString removeAttribute:NSBackgroundColorAttributeName range:fullRange];
+- (void)removeAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ NSArray *mentions = [self findAllOccurences:range];
- MentionStyleProps *props = [self stylePropsWithParams:pair.styleValue];
- if (props.decorationLine == DecorationUnderline) {
- [attributedString removeAttribute:NSUnderlineStyleAttributeName range:fullRange];
- }
+ for (StylePair *pair in mentions) {
+ NSRange fullRange =
+ [self getFullMentionRangeInAttributedString:attributedString
+ atIndex:[pair.rangeValue rangeValue]
+ .location];
+
+ [attributedString removeAttribute:MentionAttributeName range:fullRange];
+
+ // restore normal coloring
+ UIColor *primary = [_input->config primaryColor];
+ [attributedString addAttribute:NSForegroundColorAttributeName
+ value:primary
+ range:fullRange];
+ [attributedString addAttribute:NSUnderlineColorAttributeName
+ value:primary
+ range:fullRange];
+ [attributedString addAttribute:NSStrikethroughColorAttributeName
+ value:primary
+ range:fullRange];
+ [attributedString removeAttribute:NSBackgroundColorAttributeName
+ range:fullRange];
+
+ MentionStyleProps *props = [self stylePropsWithParams:pair.styleValue];
+ if (props.decorationLine == DecorationUnderline) {
+ [attributedString removeAttribute:NSUnderlineStyleAttributeName
+ range:fullRange];
}
+ }
}
// we have to make sure all mentions get removed properly
@@ -172,15 +182,15 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
return params != nullptr;
}
-- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString
- range:(NSRange)range
-{
- return [OccurenceUtils detect:MentionAttributeName
- inString:attributedString
- inRange:range
- withCondition:^BOOL(id value, NSRange r) {
- return [self styleCondition:value :r];
- }];
+- (BOOL)detectStyleInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils detect:MentionAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id value, NSRange r) {
+ return [self styleCondition:value:r];
+ }];
}
- (BOOL)detectStyle:(NSRange)range {
@@ -730,59 +740,58 @@ - (void)removeActiveMentionRange {
}
}
-- (NSRange)getFullMentionRangeInAttributedString:(NSMutableAttributedString *)attrString
- atIndex:(NSUInteger)location
-{
- NSRange full = NSMakeRange(0, 0);
- NSRange bounds = NSMakeRange(0, attrString.length);
+- (NSRange)getFullMentionRangeInAttributedString:
+ (NSMutableAttributedString *)attrString
+ atIndex:(NSUInteger)location {
+ NSRange full = NSMakeRange(0, 0);
+ NSRange bounds = NSMakeRange(0, attrString.length);
- if (location >= attrString.length && attrString.length > 0) {
- location = attrString.length - 1;
- }
+ if (location >= attrString.length && attrString.length > 0) {
+ location = attrString.length - 1;
+ }
- [attrString attribute:MentionAttributeName
- atIndex:location
- longestEffectiveRange:&full
- inRange:bounds];
+ [attrString attribute:MentionAttributeName
+ atIndex:location
+ longestEffectiveRange:&full
+ inRange:bounds];
- return full;
+ return full;
}
- (void)addMentionInAttributedString:(NSMutableAttributedString *)string
range:(NSRange)range
- params:(MentionParams *)params
-{
- if (!string || !params) return;
+ params:(MentionParams *)params {
+ if (!string || !params)
+ return;
+
+ MentionStyleProps *props =
+ [_input->config mentionStylePropsForIndicator:params.indicator];
- MentionStyleProps *props =
- [_input->config mentionStylePropsForIndicator:params.indicator];
+ NSMutableDictionary *attrs =
+ [_input->textView.typingAttributes mutableCopy];
- NSMutableDictionary *attrs =
- [_input->textView.typingAttributes mutableCopy];
-
- attrs[MentionAttributeName] = params;
- attrs[NSForegroundColorAttributeName] = props.color;
- attrs[NSUnderlineColorAttributeName] = props.color;
- attrs[NSStrikethroughColorAttributeName] = props.color;
- attrs[NSBackgroundColorAttributeName] =
- [props.backgroundColor colorWithAlphaIfNotTransparent:0.4];
+ attrs[MentionAttributeName] = params;
+ attrs[NSForegroundColorAttributeName] = props.color;
+ attrs[NSUnderlineColorAttributeName] = props.color;
+ attrs[NSStrikethroughColorAttributeName] = props.color;
+ attrs[NSBackgroundColorAttributeName] =
+ [props.backgroundColor colorWithAlphaIfNotTransparent:0.4];
- if (props.decorationLine == DecorationUnderline) {
- attrs[NSUnderlineStyleAttributeName] = @(NSUnderlineStyleSingle);
- } else {
- [attrs removeObjectForKey:NSUnderlineStyleAttributeName];
- }
+ if (props.decorationLine == DecorationUnderline) {
+ attrs[NSUnderlineStyleAttributeName] = @(NSUnderlineStyleSingle);
+ } else {
+ [attrs removeObjectForKey:NSUnderlineStyleAttributeName];
+ }
- NSString *mentionText = params.text ?: @"";
- NSAttributedString *mention =
- [[NSAttributedString alloc] initWithString:mentionText attributes:attrs];
+ NSString *mentionText = params.text ?: @"";
+ NSAttributedString *mention =
+ [[NSAttributedString alloc] initWithString:mentionText attributes:attrs];
- if (range.length == 0) {
- [string insertAttributedString:mention atIndex:range.location];
- } else {
- [string replaceCharactersInRange:range withAttributedString:mention];
- }
+ if (range.length == 0) {
+ [string insertAttributedString:mention atIndex:range.location];
+ } else {
+ [string replaceCharactersInRange:range withAttributedString:mention];
+ }
}
-
@end
diff --git a/ios/styles/OrderedListStyle.mm b/ios/styles/OrderedListStyle.mm
index 7ba1d3e14..690295c9e 100644
--- a/ios/styles/OrderedListStyle.mm
+++ b/ios/styles/OrderedListStyle.mm
@@ -33,62 +33,68 @@ - (instancetype)initWithInput:(id)input {
- (void)applyStyle:(NSRange)range {
BOOL isStylePresent = [self detectStyle:range];
if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributes:range withTypingAttr:YES];
+ isStylePresent ? [self removeAttributes:range] : [self addAttributes:range];
} else {
isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
}
}
-- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString
- range:(NSRange)range
-{
- NSTextList *numberBullet = [[NSTextList alloc] initWithMarkerFormat:NSTextListMarkerDecimal options:0];
- NSArray *paragraphs =
- [ParagraphsUtils getSeparateParagraphsRangesInAttributedString:attributedString range:range];
-
- NSInteger offset = 0;
-
- for (NSValue *val in paragraphs) {
- NSRange p = val.rangeValue;
- NSRange fixedRange = NSMakeRange(p.location + offset, p.length);
-
- // Fill empty lines with ZWSP so the paragraph style can apply to at least 1 char
- if (fixedRange.length == 0 ||
- (fixedRange.length == 1 &&
- [[NSCharacterSet newlineCharacterSet] characterIsMember:
- [attributedString.string characterAtIndex:fixedRange.location]])) {
-
- [TextInsertionUtils insertTextInAttributedString:@"\u200B"
- at:fixedRange.location
- additionalAttributes:nil
- attributedString:attributedString];
-
- fixedRange = NSMakeRange(fixedRange.location, fixedRange.length + 1);
- offset += 1;
- }
-
- [attributedString enumerateAttribute:NSParagraphStyleAttributeName
- inRange:fixedRange
- options:0
- usingBlock:^(id _Nullable value, NSRange subRange, BOOL * _Nonnull stop)
- {
- NSMutableParagraphStyle *pStyle = value ? [(NSParagraphStyle *)value mutableCopy]
- : [NSMutableParagraphStyle new];
-
- pStyle.textLists = @[ numberBullet ];
- pStyle.headIndent = [self getHeadIndent];
- pStyle.firstLineHeadIndent = [self getHeadIndent];
- NSMutableDictionary *typingAttrs = [_input->textView.typingAttributes mutableCopy];
- typingAttrs[NSParagraphStyleAttributeName] = pStyle;
- [attributedString addAttribute:NSParagraphStyleAttributeName value:pStyle range:subRange];
- }];
+- (void)addAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ NSTextList *numberBullet =
+ [[NSTextList alloc] initWithMarkerFormat:NSTextListMarkerDecimal
+ options:0];
+ NSArray *paragraphs = [ParagraphsUtils
+ getSeparateParagraphsRangesInAttributedString:attributedString
+ range:range];
+
+ NSInteger offset = 0;
+
+ for (NSValue *val in paragraphs) {
+ NSRange p = val.rangeValue;
+ NSRange fixedRange = NSMakeRange(p.location + offset, p.length);
+
+ if (fixedRange.length == 0 ||
+ (fixedRange.length == 1 &&
+ [[NSCharacterSet newlineCharacterSet]
+ characterIsMember:[attributedString.string
+ characterAtIndex:fixedRange.location]])) {
+
+ [TextInsertionUtils insertTextInAttributedString:@"\u200B"
+ at:fixedRange.location
+ additionalAttributes:nil
+ attributedString:attributedString];
+
+ fixedRange = NSMakeRange(fixedRange.location, fixedRange.length + 1);
+ offset += 1;
}
-}
+ [attributedString
+ enumerateAttribute:NSParagraphStyleAttributeName
+ inRange:fixedRange
+ options:0
+ usingBlock:^(id _Nullable value, NSRange subRange,
+ BOOL *_Nonnull stop) {
+ NSMutableParagraphStyle *pStyle =
+ value ? [(NSParagraphStyle *)value mutableCopy]
+ : [NSMutableParagraphStyle new];
+
+ pStyle.textLists = @[ numberBullet ];
+ pStyle.headIndent = [self getHeadIndent];
+ pStyle.firstLineHeadIndent = [self getHeadIndent];
+ NSMutableDictionary *typingAttrs =
+ [_input->textView.typingAttributes mutableCopy];
+ typingAttrs[NSParagraphStyleAttributeName] = pStyle;
+ [attributedString addAttribute:NSParagraphStyleAttributeName
+ value:pStyle
+ range:subRange];
+ }];
+ }
+}
// we assume correct paragraph range is already given
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
+- (void)addAttributes:(NSRange)range {
NSTextList *numberBullet =
[[NSTextList alloc] initWithMarkerFormat:NSTextListMarkerDecimal
options:0];
@@ -157,46 +163,55 @@ - (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
}
// 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;
- }
+ 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];
+ [self addAttributes:_input->textView.selectedRange];
}
-- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesInAttributedString:attributedString range:range];
-
- for(NSValue *value in paragraphs) {
+- (void)removeAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ NSArray *paragraphs = [ParagraphsUtils
+ getSeparateParagraphsRangesInAttributedString:attributedString
+ range:range];
+
+ for (NSValue *value in paragraphs) {
NSRange range = [value rangeValue];
- [attributedString 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;
- [attributedString addAttribute:NSParagraphStyleAttributeName value:pStyle range:range];
- }
- ];
+ [attributedString 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;
+ [attributedString
+ addAttribute:NSParagraphStyleAttributeName
+ value:pStyle
+ range:range];
+ }];
}
}
- (void)removeAttributes:(NSRange)range {
[_input->textView.textStorage beginEditing];
-
- [self removeAttributesInAttributedString:_input->textView.textStorage range:range];
-
+
+ [self removeAttributesInAttributedString:_input->textView.textStorage
+ range:range];
+
[_input->textView.textStorage endEditing];
// also remove typing attributes
@@ -268,8 +283,7 @@ - (BOOL)tryHandlingListShorcutInRange:(NSRange)range
// add attributes on the paragraph
[self addAttributes:NSMakeRange(paragraphRange.location,
- paragraphRange.length - 1)
- withTypingAttr:YES];
+ paragraphRange.length - 1)];
return YES;
}
}
@@ -284,17 +298,21 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
NSTextListMarkerDecimal;
}
-- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- return [OccurenceUtils detect:NSParagraphStyleAttributeName inString:attributedString inRange:range
- withCondition: ^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value :range];
- }
- ];
+- (BOOL)detectStyleInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils detect:NSParagraphStyleAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
}
- (BOOL)detectStyle:(NSRange)range {
- if(range.length >= 1) {
- return [self detectStyleInAttributedString:_input->textView.textStorage range:range];
+ if (range.length >= 1) {
+ return [self detectStyleInAttributedString:_input->textView.textStorage
+ range:range];
} else {
return [OccurenceUtils detect:NSParagraphStyleAttributeName
withInput:_input
diff --git a/ios/styles/StrikethroughStyle.mm b/ios/styles/StrikethroughStyle.mm
index 7060d4cbf..8eaf26b14 100644
--- a/ios/styles/StrikethroughStyle.mm
+++ b/ios/styles/StrikethroughStyle.mm
@@ -23,19 +23,23 @@ - (instancetype)initWithInput:(id)input {
- (void)applyStyle:(NSRange)range {
BOOL isStylePresent = [self detectStyle:range];
if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributes:range withTypingAttr:YES];
+ isStylePresent ? [self removeAttributes:range] : [self addAttributes:range];
} else {
isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
}
}
-- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- [attributedString addAttribute:NSStrikethroughStyleAttributeName value:@(NSUnderlineStyleSingle) range:range];
+- (void)addAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ [attributedString addAttribute:NSStrikethroughStyleAttributeName
+ value:@(NSUnderlineStyleSingle)
+ range:range];
}
- (void)addAttributes:(NSRange)range {
- [self addAttributesInAttributedString: _input->textView.textStorage range:range];
+ [self addAttributesInAttributedString:_input->textView.textStorage
+ range:range];
}
- (void)addTypingAttributes {
@@ -45,12 +49,16 @@ - (void)addTypingAttributes {
_input->textView.typingAttributes = newTypingAttrs;
}
-- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- [attributedString removeAttribute:NSStrikethroughStyleAttributeName range:range];
+- (void)removeAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ [attributedString removeAttribute:NSStrikethroughStyleAttributeName
+ range:range];
}
- (void)removeAttributes:(NSRange)range {
- [self removeAttributesInAttributedString: _input->textView.textStorage range:range];
+ [self removeAttributesInAttributedString:_input->textView.textStorage
+ range:range];
}
- (void)removeTypingAttributes {
@@ -66,17 +74,21 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
[strikethroughStyle intValue] != NSUnderlineStyleNone;
}
-- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- return [OccurenceUtils detect:NSStrikethroughStyleAttributeName inString:attributedString inRange:range
- withCondition: ^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value :range];
- }
- ];
+- (BOOL)detectStyleInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils detect:NSStrikethroughStyleAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
}
- (BOOL)detectStyle:(NSRange)range {
- if(range.length >= 1) {
- return [self detectStyleInAttributedString:_input->textView.textStorage range:range];
+ if (range.length >= 1) {
+ return [self detectStyleInAttributedString:_input->textView.textStorage
+ range:range];
} else {
return [OccurenceUtils detect:NSStrikethroughStyleAttributeName
withInput:_input
diff --git a/ios/styles/UnderlineStyle.mm b/ios/styles/UnderlineStyle.mm
index 427564f6d..ff2960d3a 100644
--- a/ios/styles/UnderlineStyle.mm
+++ b/ios/styles/UnderlineStyle.mm
@@ -23,19 +23,23 @@ - (instancetype)initWithInput:(id)input {
- (void)applyStyle:(NSRange)range {
BOOL isStylePresent = [self detectStyle:range];
if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributes:range withTypingAttr:YES];
+ isStylePresent ? [self removeAttributes:range] : [self addAttributes:range];
} else {
isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
}
}
-- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- [attributedString addAttribute:NSUnderlineStyleAttributeName value:@(NSUnderlineStyleSingle) range:range];
+- (void)addAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ [attributedString addAttribute:NSUnderlineStyleAttributeName
+ value:@(NSUnderlineStyleSingle)
+ range:range];
}
- (void)addAttributes:(NSRange)range {
- [self addAttributesInAttributedString: _input->textView.textStorage range:range];
+ [self addAttributesInAttributedString:_input->textView.textStorage
+ range:range];
}
- (void)addTypingAttributes {
@@ -45,12 +49,15 @@ - (void)addTypingAttributes {
_input->textView.typingAttributes = newTypingAttrs;
}
-- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
+- (void)removeAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
[attributedString removeAttribute:NSUnderlineStyleAttributeName range:range];
}
- (void)removeAttributes:(NSRange)range {
- [self removeAttributesInAttributedString: _input->textView.textStorage range:range];
+ [self removeAttributesInAttributedString:_input->textView.textStorage
+ range:range];
}
- (void)removeTypingAttributes {
@@ -104,17 +111,21 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
![self underlinedMentionConflictsInRange:range];
}
-- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- return [OccurenceUtils detect:NSStrikethroughStyleAttributeName inString:attributedString inRange:range
- withCondition: ^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value :range];
- }
- ];
+- (BOOL)detectStyleInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils detect:NSStrikethroughStyleAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
}
- (BOOL)detectStyle:(NSRange)range {
- if(range.length >= 1) {
- return [self detectStyleInAttributedString:_input->textView.textStorage range:range];
+ if (range.length >= 1) {
+ return [self detectStyleInAttributedString:_input->textView.textStorage
+ range:range];
} else {
return [OccurenceUtils detect:NSUnderlineStyleAttributeName
withInput:_input
diff --git a/ios/styles/UnorderedListStyle.mm b/ios/styles/UnorderedListStyle.mm
index 468c43b8d..d3672a50e 100644
--- a/ios/styles/UnorderedListStyle.mm
+++ b/ios/styles/UnorderedListStyle.mm
@@ -33,50 +33,68 @@ - (instancetype)initWithInput:(id)input {
- (void)applyStyle:(NSRange)range {
BOOL isStylePresent = [self detectStyle:range];
if (range.length >= 1) {
- isStylePresent ? [self removeAttributes:range]
- : [self addAttributes:range withTypingAttr:YES];
+ isStylePresent ? [self removeAttributes:range] : [self addAttributes:range];
} else {
isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
}
}
-- (void)addAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- NSTextList *bullet = [[NSTextList alloc] initWithMarkerFormat:NSTextListMarkerDisc options:0];
- NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesInAttributedString:attributedString range:range];
- // if we fill empty lines with zero width spaces, we need to offset later ranges
+- (void)addAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ NSTextList *bullet =
+ [[NSTextList alloc] initWithMarkerFormat:NSTextListMarkerDisc options:0];
+ NSArray *paragraphs = [ParagraphsUtils
+ getSeparateParagraphsRangesInAttributedString:attributedString
+ range:range];
+ // if we fill empty lines with zero width spaces, we need to offset later
+ // ranges
NSInteger offset = 0;
-
- for(NSValue *value in paragraphs) {
+
+ 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: [attributedString.string characterAtIndex:fixedRange.location]])
- ) {
- [TextInsertionUtils insertTextInAttributedString:@"\u200B" at:fixedRange.location additionalAttributes:nullptr attributedString: attributedString];
+ 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:[attributedString.string
+ characterAtIndex:fixedRange.location]])) {
+ [TextInsertionUtils insertTextInAttributedString:@"\u200B"
+ at:fixedRange.location
+ additionalAttributes:nullptr
+ attributedString:attributedString];
fixedRange = NSMakeRange(fixedRange.location, fixedRange.length + 1);
offset += 1;
}
-
- [attributedString enumerateAttribute:NSParagraphStyleAttributeName inRange:fixedRange options:0
- usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
- NSMutableParagraphStyle *pStyle = value ? [(NSParagraphStyle *)value mutableCopy]
- : [NSMutableParagraphStyle new];
- pStyle.textLists = @[bullet];
- pStyle.headIndent = [self getHeadIndent];
- pStyle.firstLineHeadIndent = [self getHeadIndent];
- NSMutableDictionary *typingAttrs = [_input->textView.typingAttributes mutableCopy];
- typingAttrs[NSParagraphStyleAttributeName] = pStyle;
- [attributedString addAttribute:NSParagraphStyleAttributeName value:pStyle range:range];
- }
- ];
+
+ [attributedString
+ enumerateAttribute:NSParagraphStyleAttributeName
+ inRange:fixedRange
+ options:0
+ usingBlock:^(id _Nullable value, NSRange range,
+ BOOL *_Nonnull stop) {
+ NSMutableParagraphStyle *pStyle =
+ value ? [(NSParagraphStyle *)value mutableCopy]
+ : [NSMutableParagraphStyle new];
+ pStyle.textLists = @[ bullet ];
+ pStyle.headIndent = [self getHeadIndent];
+ pStyle.firstLineHeadIndent = [self getHeadIndent];
+ NSMutableDictionary *typingAttrs =
+ [_input->textView.typingAttributes mutableCopy];
+ typingAttrs[NSParagraphStyleAttributeName] = pStyle;
+ [attributedString addAttribute:NSParagraphStyleAttributeName
+ value:pStyle
+ range:range];
+ }];
}
}
// we assume correct paragraph range is already given
-- (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
+- (void)addAttributes:(NSRange)range {
NSTextList *bullet =
[[NSTextList alloc] initWithMarkerFormat:NSTextListMarkerDisc options:0];
NSArray *paragraphs =
@@ -144,44 +162,53 @@ - (void)addAttributes:(NSRange)range withTypingAttr:(BOOL)withTypingAttr {
}
// 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;
- }
+ 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];
+ [self addAttributes:_input->textView.selectedRange];
}
-- (void)removeAttributesInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesInAttributedString:attributedString range:range];
-
- for(NSValue *value in paragraphs) {
+- (void)removeAttributesInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ NSArray *paragraphs = [ParagraphsUtils
+ getSeparateParagraphsRangesInAttributedString:attributedString
+ range:range];
+
+ for (NSValue *value in paragraphs) {
NSRange range = [value rangeValue];
- [attributedString 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;
- [attributedString addAttribute:NSParagraphStyleAttributeName value:pStyle range:range];
- }
- ];
+ [attributedString 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;
+ [attributedString
+ addAttribute:NSParagraphStyleAttributeName
+ value:pStyle
+ range:range];
+ }];
}
}
- (void)removeAttributes:(NSRange)range {
[_input->textView.textStorage beginEditing];
- [self addAttributesInAttributedString:_input->textView.textStorage range:range];
+ [self removeAttributesInAttributedString:_input->textView.textStorage
+ range:range];
[_input->textView.textStorage endEditing];
// also remove typing attributes
@@ -253,8 +280,7 @@ - (BOOL)tryHandlingListShorcutInRange:(NSRange)range
// add attributes on the dashless paragraph
[self addAttributes:NSMakeRange(paragraphRange.location,
- paragraphRange.length - 1)
- withTypingAttr:YES];
+ paragraphRange.length - 1)];
return YES;
}
}
@@ -268,17 +294,21 @@ - (BOOL)styleCondition:(id _Nullable)value:(NSRange)range {
paragraph.textLists.firstObject.markerFormat == NSTextListMarkerDisc;
}
-- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString *)attributedString range:(NSRange)range {
- return [OccurenceUtils detect:NSParagraphStyleAttributeName inString:attributedString inRange:range
- withCondition: ^BOOL(id _Nullable value, NSRange range) {
- return [self styleCondition:value :range];
- }
- ];
+- (BOOL)detectStyleInAttributedString:
+ (NSMutableAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils detect:NSParagraphStyleAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
}
- (BOOL)detectStyle:(NSRange)range {
- if(range.length >= 1) {
- return [self detectStyleInAttributedString:_input->textView.textStorage range:range];
+ if (range.length >= 1) {
+ return [self detectStyleInAttributedString:_input->textView.textStorage
+ range:range];
} else {
return [OccurenceUtils detect:NSParagraphStyleAttributeName
withInput:_input
diff --git a/ios/utils/BaseStyleProtocol.h b/ios/utils/BaseStyleProtocol.h
index ca78b1b33..a32a0ae63 100644
--- a/ios/utils/BaseStyleProtocol.h
+++ b/ios/utils/BaseStyleProtocol.h
@@ -8,9 +8,15 @@
- (instancetype _Nonnull)initWithInput:(id _Nonnull)input;
- (void)applyStyle:(NSRange)range;
- (void)addAttributes:(NSRange)range;
-- (void)addAttributesInAttributedString:(NSMutableAttributedString * _Nonnull)attributedString range:(NSRange)range;
-- (void)removeAttributesInAttributedString:(NSMutableAttributedString * _Nonnull)attributedString range:(NSRange)range;
-- (BOOL)detectStyleInAttributedString:(NSMutableAttributedString * _Nonnull)attributedString range:(NSRange)range;
+- (void)addAttributesInAttributedString:
+ (NSMutableAttributedString *_Nonnull)attributedString
+ range:(NSRange)range;
+- (void)removeAttributesInAttributedString:
+ (NSMutableAttributedString *_Nonnull)attributedString
+ range:(NSRange)range;
+- (BOOL)detectStyleInAttributedString:
+ (NSMutableAttributedString *_Nonnull)attributedString
+ range:(NSRange)range;
- (void)removeAttributes:(NSRange)range;
- (void)addTypingAttributes;
- (void)removeTypingAttributes;
diff --git a/ios/utils/OccurenceUtils.h b/ios/utils/OccurenceUtils.h
index 831f5e168..4596b7746 100644
--- a/ios/utils/OccurenceUtils.h
+++ b/ios/utils/OccurenceUtils.h
@@ -1,76 +1,93 @@
#pragma once
-#import
-#import "StylePair.h"
#import "EnrichedTextInputView.h"
+#import "StylePair.h"
+#import
@interface OccurenceUtils : NSObject
+ (BOOL)detect:(NSAttributedStringKey _Nonnull)key
- inString:(NSAttributedString * _Nonnull)string
- inRange:(NSRange)range
- withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
+ inString:(NSAttributedString *_Nonnull)string
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
+ NSRange range))condition;
-+ (BOOL)detectMultiple:(NSArray * _Nonnull)keys
- inString:(NSAttributedString * _Nonnull)string
- inRange:(NSRange)range
- withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
++ (BOOL)detectMultiple:(NSArray *_Nonnull)keys
+ inString:(NSAttributedString *_Nonnull)string
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
+ NSRange range))condition;
+ (BOOL)any:(NSAttributedStringKey _Nonnull)key
- inString:(NSAttributedString * _Nonnull)string
- inRange:(NSRange)range
-withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
+ inString:(NSAttributedString *_Nonnull)string
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
+ NSRange range))condition;
-+ (BOOL)anyMultiple:(NSArray * _Nonnull)keys
- inString:(NSAttributedString * _Nonnull)string
- inRange:(NSRange)range
- withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
++ (BOOL)anyMultiple:(NSArray *_Nonnull)keys
+ inString:(NSAttributedString *_Nonnull)string
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
+ NSRange range))condition;
-+ (NSArray * _Nonnull)all:(NSAttributedStringKey _Nonnull)key
- inString:(NSAttributedString * _Nonnull)string
- inRange:(NSRange)range
- withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
++ (NSArray *_Nonnull)all:(NSAttributedStringKey _Nonnull)key
+ inString:(NSAttributedString *_Nonnull)string
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^
+ _Nonnull)(id _Nullable value,
+ NSRange range))condition;
-+ (NSArray * _Nonnull)allMultiple:(NSArray * _Nonnull)keys
- inString:(NSAttributedString * _Nonnull)string
- inRange:(NSRange)range
- withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
++ (NSArray *_Nonnull)
+ allMultiple:(NSArray *_Nonnull)keys
+ inString:(NSAttributedString *_Nonnull)string
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
+ NSRange range))condition;
+ (BOOL)detect:(NSAttributedStringKey _Nonnull)key
- withInput:(EnrichedTextInputView * _Nonnull)input
- inRange:(NSRange)range
- withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
+ withInput:(EnrichedTextInputView *_Nonnull)input
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
+ NSRange range))condition;
+ (BOOL)detect:(NSAttributedStringKey _Nonnull)key
- withInput:(EnrichedTextInputView * _Nonnull)input
- atIndex:(NSUInteger)index
- checkPrevious:(BOOL)checkPrev
- withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
+ withInput:(EnrichedTextInputView *_Nonnull)input
+ atIndex:(NSUInteger)index
+ checkPrevious:(BOOL)checkPrev
+ withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
+ NSRange range))condition;
-+ (BOOL)detectMultiple:(NSArray * _Nonnull)keys
- withInput:(EnrichedTextInputView * _Nonnull)input
++ (BOOL)detectMultiple:(NSArray *_Nonnull)keys
+ withInput:(EnrichedTextInputView *_Nonnull)input
inRange:(NSRange)range
- withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
+ withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
+ NSRange range))condition;
+ (BOOL)any:(NSAttributedStringKey _Nonnull)key
- withInput:(EnrichedTextInputView * _Nonnull)input
- inRange:(NSRange)range
-withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
+ withInput:(EnrichedTextInputView *_Nonnull)input
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
+ NSRange range))condition;
-+ (BOOL)anyMultiple:(NSArray * _Nonnull)keys
- withInput:(EnrichedTextInputView * _Nonnull)input
++ (BOOL)anyMultiple:(NSArray *_Nonnull)keys
+ withInput:(EnrichedTextInputView *_Nonnull)input
inRange:(NSRange)range
- withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
+ withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
+ NSRange range))condition;
-+ (NSArray * _Nonnull)all:(NSAttributedStringKey _Nonnull)key
- withInput:(EnrichedTextInputView * _Nonnull)input
- inRange:(NSRange)range
- withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
++ (NSArray *_Nonnull)all:(NSAttributedStringKey _Nonnull)key
+ withInput:(EnrichedTextInputView *_Nonnull)input
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^
+ _Nonnull)(id _Nullable value,
+ NSRange range))condition;
-+ (NSArray * _Nonnull)allMultiple:(NSArray * _Nonnull)keys
- withInput:(EnrichedTextInputView * _Nonnull)input
- inRange:(NSRange)range
- withCondition:(BOOL (NS_NOESCAPE ^_Nonnull)(id _Nullable value, NSRange range))condition;
++ (NSArray *_Nonnull)
+ allMultiple:(NSArray *_Nonnull)keys
+ withInput:(EnrichedTextInputView *_Nonnull)input
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^ _Nonnull)(id _Nullable value,
+ NSRange range))condition;
-+ (NSArray * _Nonnull)getRangesWithout:(NSArray * _Nonnull)types
- withInput:(EnrichedTextInputView * _Nonnull)input
- inRange:(NSRange)range;
++ (NSArray *_Nonnull)getRangesWithout:(NSArray *_Nonnull)types
+ withInput:(EnrichedTextInputView *_Nonnull)input
+ inRange:(NSRange)range;
@end
diff --git a/ios/utils/OccurenceUtils.mm b/ios/utils/OccurenceUtils.mm
index 4c7d38f8a..711abd73d 100644
--- a/ios/utils/OccurenceUtils.mm
+++ b/ios/utils/OccurenceUtils.mm
@@ -2,331 +2,326 @@
@interface OccurenceUtils ()
+ (void)enumerateAttributes:(NSArray *)keys
- inString:(NSAttributedString *)string
+ inString:(NSAttributedString *)string
inRange:(NSRange)range
- withBlock:(void(NS_NOESCAPE ^)(NSAttributedStringKey key, id value, NSRange range, BOOL *stop))block;
-
-+ (NSArray *)collectAttributes:(NSArray *)keys
- inString:(NSAttributedString *)string
- inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition;
+ withBlock:(void(NS_NOESCAPE ^)(NSAttributedStringKey key,
+ id value, NSRange range,
+ BOOL *stop))block;
+
++ (NSArray *)
+ collectAttributes:(NSArray *)keys
+ inString:(NSAttributedString *)string
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition;
@end
-
@implementation OccurenceUtils
+ (void)enumerateAttributes:(NSArray *)keys
- inString:(NSAttributedString *)string
+ inString:(NSAttributedString *)string
inRange:(NSRange)range
- withBlock:(void(NS_NOESCAPE ^)(NSAttributedStringKey key,
- id value,
- NSRange range,
- BOOL *stop))block
-{
- __block BOOL outerStop = NO;
-
- for (NSAttributedStringKey key in keys) {
-
- [string enumerateAttribute:key
- inRange:range
- options:0
- usingBlock:^(id value, NSRange subRange, BOOL *innerStop)
- {
- block(key, value, subRange, &outerStop);
- if (outerStop) {
- *innerStop = YES;
- }
- }];
-
- if (outerStop) break;
- }
-}
+ withBlock:(void(NS_NOESCAPE ^)(NSAttributedStringKey key,
+ id value, NSRange range,
+ BOOL *stop))block {
+ __block BOOL outerStop = NO;
-+ (NSArray *)collectAttributes:(NSArray *)keys
- inString:(NSAttributedString *)string
- inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
-{
- NSMutableArray *result = [NSMutableArray array];
+ for (NSAttributedStringKey key in keys) {
- [self enumerateAttributes:keys
- inString:string
+ [string enumerateAttribute:key
inRange:range
- withBlock:^(NSAttributedStringKey key, id value, NSRange attrRange, BOOL *stop)
- {
- if (condition(value, attrRange)) {
- StylePair *pair = [StylePair new];
- pair.rangeValue = [NSValue valueWithRange:attrRange];
- pair.styleValue = value;
- [result addObject:pair];
- }
- }];
-
- return result;
+ options:0
+ usingBlock:^(id value, NSRange subRange, BOOL *innerStop) {
+ block(key, value, subRange, &outerStop);
+ if (outerStop) {
+ *innerStop = YES;
+ }
+ }];
+
+ if (outerStop)
+ break;
+ }
}
++ (NSArray *)
+ collectAttributes:(NSArray *)keys
+ inString:(NSAttributedString *)string
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition {
+ NSMutableArray *result = [NSMutableArray array];
+
+ [self enumerateAttributes:keys
+ inString:string
+ inRange:range
+ withBlock:^(NSAttributedStringKey key, id value,
+ NSRange attrRange, BOOL *stop) {
+ if (condition(value, attrRange)) {
+ StylePair *pair = [StylePair new];
+ pair.rangeValue = [NSValue valueWithRange:attrRange];
+ pair.styleValue = value;
+ [result addObject:pair];
+ }
+ }];
+
+ return result;
+}
#pragma mark - ============================================================
#pragma mark Public API (Attributed String Versions)
#pragma mark - ============================================================
+ (BOOL)detect:(NSAttributedStringKey)key
- inString:(NSAttributedString *)string
- inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
-{
- __block NSInteger total = 0;
-
- [self enumerateAttributes:@[key]
- inString:string
- inRange:range
- withBlock:^(NSAttributedStringKey key, id value, NSRange r, BOOL *stop)
- {
- if (condition(value, r)) {
- total += r.length;
- }
- }];
-
- return total == range.length;
+ inString:(NSAttributedString *)string
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition {
+ __block NSInteger total = 0;
+
+ [self enumerateAttributes:@[ key ]
+ inString:string
+ inRange:range
+ withBlock:^(NSAttributedStringKey key, id value, NSRange r,
+ BOOL *stop) {
+ if (condition(value, r)) {
+ total += r.length;
+ }
+ }];
+
+ return total == range.length;
}
+ (BOOL)detectMultiple:(NSArray *)keys
inString:(NSAttributedString *)string
- inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
-{
- __block NSInteger total = 0;
-
- [self enumerateAttributes:keys
- inString:string
- inRange:range
- withBlock:^(NSAttributedStringKey key, id value, NSRange r, BOOL *stop)
- {
- if (condition(value, r)) {
- total += r.length;
- }
- }];
-
- return total == range.length;
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition {
+ __block NSInteger total = 0;
+
+ [self enumerateAttributes:keys
+ inString:string
+ inRange:range
+ withBlock:^(NSAttributedStringKey key, id value, NSRange r,
+ BOOL *stop) {
+ if (condition(value, r)) {
+ total += r.length;
+ }
+ }];
+
+ return total == range.length;
}
+ (BOOL)any:(NSAttributedStringKey)key
- inString:(NSAttributedString *)string
- inRange:(NSRange)range
-withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
-{
- __block BOOL found = NO;
-
- [self enumerateAttributes:@[key]
- inString:string
- inRange:range
- withBlock:^(NSAttributedStringKey key, id value, NSRange r, BOOL *stop)
- {
- if (condition(value, r)) {
- found = YES;
- *stop = YES;
- }
- }];
-
- return found;
+ inString:(NSAttributedString *)string
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition {
+ __block BOOL found = NO;
+
+ [self enumerateAttributes:@[ key ]
+ inString:string
+ inRange:range
+ withBlock:^(NSAttributedStringKey key, id value, NSRange r,
+ BOOL *stop) {
+ if (condition(value, r)) {
+ found = YES;
+ *stop = YES;
+ }
+ }];
+
+ return found;
}
+ (BOOL)anyMultiple:(NSArray *)keys
inString:(NSAttributedString *)string
- inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
-{
- __block BOOL found = NO;
-
- [self enumerateAttributes:keys
- inString:string
- inRange:range
- withBlock:^(NSAttributedStringKey key, id value, NSRange r, BOOL *stop)
- {
- if (condition(value, r)) {
- found = YES;
- *stop = YES;
- }
- }];
-
- return found;
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition {
+ __block BOOL found = NO;
+
+ [self enumerateAttributes:keys
+ inString:string
+ inRange:range
+ withBlock:^(NSAttributedStringKey key, id value, NSRange r,
+ BOOL *stop) {
+ if (condition(value, r)) {
+ found = YES;
+ *stop = YES;
+ }
+ }];
+
+ return found;
}
+ (NSArray *)all:(NSAttributedStringKey)key
inString:(NSAttributedString *)string
- inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
-{
- return [self collectAttributes:@[key]
- inString:string
- inRange:range
- withCondition:condition];
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))
+ condition {
+ return [self collectAttributes:@[ key ]
+ inString:string
+ inRange:range
+ withCondition:condition];
}
+ (NSArray *)allMultiple:(NSArray *)keys
inString:(NSAttributedString *)string
- inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
-{
- return [self collectAttributes:keys
- inString:string
- inRange:range
- withCondition:condition];
+ inRange:(NSRange)range
+ withCondition:
+ (BOOL(NS_NOESCAPE ^)(id value, NSRange range))
+ condition {
+ return [self collectAttributes:keys
+ inString:string
+ inRange:range
+ withCondition:condition];
}
-
#pragma mark - ============================================================
#pragma mark Public API (EnrichedTextInputView Versions)
#pragma mark - ============================================================
/// detects on a range using input->textView.textStorage
+ (BOOL)detect:(NSAttributedStringKey)key
- withInput:(EnrichedTextInputView *)input
- inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
-{
- return [self detect:key
- inString:input->textView.textStorage
- inRange:range
- withCondition:condition];
+ withInput:(EnrichedTextInputView *)input
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition {
+ return [self detect:key
+ inString:input->textView.textStorage
+ inRange:range
+ withCondition:condition];
}
/// detects at index (typing attributes logic preserved)
+ (BOOL)detect:(NSAttributedStringKey)key
- withInput:(EnrichedTextInputView *)input
- atIndex:(NSUInteger)index
- checkPrevious:(BOOL)checkPrev
- withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
-{
- NSRange detectionRange = NSMakeRange(index, 0);
- id attrValue;
-
- if (NSEqualRanges(input->textView.selectedRange, detectionRange)) {
- attrValue = input->textView.typingAttributes[key];
-
- } else if (index == input->textView.textStorage.string.length) {
-
- if (checkPrev) {
- NSRange paragraph = [input->textView.textStorage.string paragraphRangeForRange:detectionRange];
- if (paragraph.location == detectionRange.location) {
- return NO;
- } else {
- return [self detect:key
- withInput:input
- inRange:NSMakeRange(paragraph.location, 1)
- withCondition:condition];
- }
- } else {
- return NO;
- }
-
+ withInput:(EnrichedTextInputView *)input
+ atIndex:(NSUInteger)index
+ checkPrevious:(BOOL)checkPrev
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition {
+ NSRange detectionRange = NSMakeRange(index, 0);
+ id attrValue;
+
+ if (NSEqualRanges(input->textView.selectedRange, detectionRange)) {
+ attrValue = input->textView.typingAttributes[key];
+
+ } else if (index == input->textView.textStorage.string.length) {
+
+ if (checkPrev) {
+ NSRange paragraph = [input->textView.textStorage.string
+ paragraphRangeForRange:detectionRange];
+ if (paragraph.location == detectionRange.location) {
+ return NO;
+ } else {
+ return [self detect:key
+ withInput:input
+ inRange:NSMakeRange(paragraph.location, 1)
+ withCondition:condition];
+ }
} else {
- NSRange eff;
- attrValue = [input->textView.textStorage attribute:key
- atIndex:index
- effectiveRange:&eff];
+ return NO;
}
- return condition(attrValue, detectionRange);
+ } else {
+ NSRange eff;
+ attrValue = [input->textView.textStorage attribute:key
+ atIndex:index
+ effectiveRange:&eff];
+ }
+
+ return condition(attrValue, detectionRange);
}
+ (BOOL)detectMultiple:(NSArray *)keys
withInput:(EnrichedTextInputView *)input
inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
-{
- return [self detectMultiple:keys
- inString:input->textView.textStorage
- inRange:range
- withCondition:condition];
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition {
+ return [self detectMultiple:keys
+ inString:input->textView.textStorage
+ inRange:range
+ withCondition:condition];
}
+ (BOOL)any:(NSAttributedStringKey)key
- withInput:(EnrichedTextInputView *)input
- inRange:(NSRange)range
-withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
-{
- return [self any:key
- inString:input->textView.textStorage
- inRange:range
- withCondition:condition];
+ withInput:(EnrichedTextInputView *)input
+ inRange:(NSRange)range
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition {
+ return [self any:key
+ inString:input->textView.textStorage
+ inRange:range
+ withCondition:condition];
}
+ (BOOL)anyMultiple:(NSArray *)keys
withInput:(EnrichedTextInputView *)input
inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
-{
- return [self anyMultiple:keys
- inString:input->textView.textStorage
- inRange:range
- withCondition:condition];
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition {
+ return [self anyMultiple:keys
+ inString:input->textView.textStorage
+ inRange:range
+ withCondition:condition];
}
+ (NSArray *)all:(NSAttributedStringKey)key
withInput:(EnrichedTextInputView *)input
inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
-{
- return [self all:key
- inString:input->textView.textStorage
- inRange:range
- withCondition:condition];
+ withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))
+ condition {
+ return [self all:key
+ inString:input->textView.textStorage
+ inRange:range
+ withCondition:condition];
}
+ (NSArray *)allMultiple:(NSArray *)keys
- withInput:(EnrichedTextInputView *)input
- inRange:(NSRange)range
- withCondition:(BOOL(NS_NOESCAPE ^)(id value, NSRange range))condition
-{
- return [self allMultiple:keys
- inString:input->textView.textStorage
- inRange:range
- withCondition:condition];
+ withInput:(EnrichedTextInputView *)input
+ inRange:(NSRange)range
+ withCondition:
+ (BOOL(NS_NOESCAPE ^)(id value, NSRange range))
+ condition {
+ return [self allMultiple:keys
+ inString:input->textView.textStorage
+ inRange:range
+ withCondition:condition];
}
+ (NSArray *)getRangesWithout:(NSArray *)types
- withInput:(EnrichedTextInputView *)input
- inRange:(NSRange)range
-{
- NSMutableArray *activeStyles = [NSMutableArray array];
-
- for (NSNumber *type in types) {
- id style = input->stylesDict[type];
- [activeStyles addObject:style];
- }
-
- if (activeStyles.count == 0) {
- return @[[NSValue valueWithRange:range]];
+ withInput:(EnrichedTextInputView *)input
+ inRange:(NSRange)range {
+ NSMutableArray *activeStyles = [NSMutableArray array];
+
+ for (NSNumber *type in types) {
+ id style = input->stylesDict[type];
+ [activeStyles addObject:style];
+ }
+
+ if (activeStyles.count == 0) {
+ return @[ [NSValue valueWithRange:range] ];
+ }
+
+ NSMutableArray *newRanges = [NSMutableArray array];
+ NSUInteger lastLocation = range.location;
+ NSUInteger end = range.location + range.length;
+
+ for (NSUInteger i = range.location; i < end; i++) {
+
+ BOOL forbidden = NO;
+ for (id style in activeStyles) {
+ if ([style detectStyle:NSMakeRange(i, 1)]) {
+ forbidden = YES;
+ break;
+ }
}
- NSMutableArray *newRanges = [NSMutableArray array];
- NSUInteger lastLocation = range.location;
- NSUInteger end = range.location + range.length;
-
- for (NSUInteger i = range.location; i < end; i++) {
-
- BOOL forbidden = NO;
- for (id style in activeStyles) {
- if ([style detectStyle:NSMakeRange(i, 1)]) {
- forbidden = YES;
- break;
- }
- }
-
- if (forbidden) {
- if (i > lastLocation) {
- [newRanges addObject:[NSValue valueWithRange:NSMakeRange(lastLocation, i - lastLocation)]];
- }
- lastLocation = i + 1;
- }
+ if (forbidden) {
+ if (i > lastLocation) {
+ [newRanges
+ addObject:[NSValue valueWithRange:NSMakeRange(lastLocation,
+ i - lastLocation)]];
+ }
+ lastLocation = i + 1;
}
+ }
- if (lastLocation < end) {
- [newRanges addObject:[NSValue valueWithRange:NSMakeRange(lastLocation, end - lastLocation)]];
- }
+ if (lastLocation < end) {
+ [newRanges
+ addObject:[NSValue valueWithRange:NSMakeRange(lastLocation,
+ end - lastLocation)]];
+ }
- return newRanges;
+ return newRanges;
}
@end
diff --git a/ios/utils/ParagraphAttributesUtils.mm b/ios/utils/ParagraphAttributesUtils.mm
index ee8f9994e..9ac7e735a 100644
--- a/ios/utils/ParagraphAttributesUtils.mm
+++ b/ios/utils/ParagraphAttributesUtils.mm
@@ -67,7 +67,7 @@ + (BOOL)handleBackspaceInRange:(NSRange)range
withSelection:YES];
typedInput->textView.typingAttributes =
typedInput->defaultTypingAttributes;
- [style addAttributes:NSMakeRange(range.location, 0) withTypingAttr:YES];
+ [style addAttributes:NSMakeRange(range.location, 0)];
return YES;
}
}
diff --git a/ios/utils/ParagraphsUtils.h b/ios/utils/ParagraphsUtils.h
index 118f2d843..0bd14c81f 100644
--- a/ios/utils/ParagraphsUtils.h
+++ b/ios/utils/ParagraphsUtils.h
@@ -5,6 +5,10 @@
+ (NSArray *)getSeparateParagraphsRangesIn:(UITextView *)textView
range:(NSRange)range;
+ (NSArray *)getNonNewlineRangesIn:(UITextView *)textView range:(NSRange)range;
-+ (NSArray *)getSeparateParagraphsRangesInAttributedString:(NSAttributedString *)attributedString range:(NSRange)range;
-+ (NSArray *)getNonNewlineRangesInAttributedString:(NSAttributedString *)attributedString range:(NSRange)range;
++ (NSArray *)getSeparateParagraphsRangesInAttributedString:
+ (NSAttributedString *)attributedString
+ range:(NSRange)range;
++ (NSArray *)getNonNewlineRangesInAttributedString:
+ (NSAttributedString *)attributedString
+ range:(NSRange)range;
@end
diff --git a/ios/utils/ParagraphsUtils.mm b/ios/utils/ParagraphsUtils.mm
index 15830f28e..277ef9df7 100644
--- a/ios/utils/ParagraphsUtils.mm
+++ b/ios/utils/ParagraphsUtils.mm
@@ -65,54 +65,69 @@ + (NSArray *)getNonNewlineRangesIn:(UITextView *)textView range:(NSRange)range {
return nonNewlineRanges;
}
-+ (NSArray *)getSeparateParagraphsRangesInAttributedString:(NSAttributedString *)attributedString range:(NSRange)range {
++ (NSArray *)getSeparateParagraphsRangesInAttributedString:
+ (NSAttributedString *)attributedString
+ range:(NSRange)range {
// just in case, get full paragraphs range
NSRange fullRange = [attributedString.string paragraphRangeForRange:range];
-
+
// we are in an empty paragraph
- if(fullRange.length == 0) {
- return @[[NSValue valueWithRange:fullRange]];
+ if (fullRange.length == 0) {
+ return @[ [NSValue valueWithRange:fullRange] ];
}
-
+
NSMutableArray *results = [[NSMutableArray alloc] init];
-
+
NSInteger lastStart = fullRange.location;
- for(int i = fullRange.location; i < fullRange.location + fullRange.length; i++) {
+ for (int i = fullRange.location; i < fullRange.location + fullRange.length;
+ i++) {
unichar currentChar = [attributedString.string characterAtIndex:i];
- if([[NSCharacterSet newlineCharacterSet] characterIsMember:currentChar]) {
- NSRange paragraphRange = [attributedString.string paragraphRangeForRange:NSMakeRange(lastStart, i - lastStart)];
- [results addObject: [NSValue valueWithRange:paragraphRange]];
- lastStart = i+1;
+ if ([[NSCharacterSet newlineCharacterSet] characterIsMember:currentChar]) {
+ NSRange paragraphRange = [attributedString.string
+ paragraphRangeForRange:NSMakeRange(lastStart, i - lastStart)];
+ [results addObject:[NSValue valueWithRange:paragraphRange]];
+ lastStart = i + 1;
}
}
-
- if(lastStart < fullRange.location + fullRange.length) {
- NSRange paragraphRange = [attributedString.string paragraphRangeForRange:NSMakeRange(lastStart, fullRange.location + fullRange.length - lastStart)];
- [results addObject: [NSValue valueWithRange:paragraphRange]];
+
+ if (lastStart < fullRange.location + fullRange.length) {
+ NSRange paragraphRange = [attributedString.string
+ paragraphRangeForRange:NSMakeRange(lastStart, fullRange.location +
+ fullRange.length -
+ lastStart)];
+ [results addObject:[NSValue valueWithRange:paragraphRange]];
}
-
+
return results;
}
-+ (NSArray *)getNonNewlineRangesInAttributedString:(NSAttributedString *)attributedString range:(NSRange)range {
++ (NSArray *)getNonNewlineRangesInAttributedString:
+ (NSAttributedString *)attributedString
+ range:(NSRange)range {
NSMutableArray *nonNewlineRanges = [[NSMutableArray alloc] init];
int lastRangeLocation = range.location;
-
- for(int i = range.location; i < range.location + range.length; i++) {
+
+ for (int i = range.location; i < range.location + range.length; i++) {
unichar currentChar = [attributedString.string characterAtIndex:i];
- if([[NSCharacterSet newlineCharacterSet] characterIsMember:currentChar]) {
- if(i - lastRangeLocation > 0) {
- [nonNewlineRanges addObject:[NSValue valueWithRange:NSMakeRange(lastRangeLocation, i - lastRangeLocation)]];
+ if ([[NSCharacterSet newlineCharacterSet] characterIsMember:currentChar]) {
+ if (i - lastRangeLocation > 0) {
+ [nonNewlineRanges
+ addObject:[NSValue
+ valueWithRange:NSMakeRange(lastRangeLocation,
+ i - lastRangeLocation)]];
}
- lastRangeLocation = i+1;
+ lastRangeLocation = i + 1;
}
}
- if(lastRangeLocation < range.location + range.length) {
- [nonNewlineRanges addObject:[NSValue valueWithRange:NSMakeRange(lastRangeLocation, range.location + range.length - lastRangeLocation)]];
+ 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/StyleHeaders.h b/ios/utils/StyleHeaders.h
index 60e3f52a6..baad6e024 100644
--- a/ios/utils/StyleHeaders.h
+++ b/ios/utils/StyleHeaders.h
@@ -30,12 +30,13 @@
- (void)manageLinkTypingAttributes;
- (void)handleAutomaticLinks:(NSString *)word inRange:(NSRange)wordRange;
- (void)handleManualLinks:(NSString *)word inRange:(NSRange)wordRange;
-- (BOOL)handleLeadingLinkReplacement:(NSRange)range replacementText:(NSString *)text;
+- (BOOL)handleLeadingLinkReplacement:(NSRange)range
+ replacementText:(NSString *)text;
- (void)addLinkInAttributedString:(NSMutableAttributedString *)attr
range:(NSRange)range
text:(NSString *)text
url:(NSString *)url
- manual:(BOOL) manual;
+ manual:(BOOL)manual;
@end
@interface MentionStyle : NSObject
diff --git a/ios/utils/TextInsertionUtils.h b/ios/utils/TextInsertionUtils.h
index 27f5b9054..1f7366747 100644
--- a/ios/utils/TextInsertionUtils.h
+++ b/ios/utils/TextInsertionUtils.h
@@ -1,7 +1,23 @@
#import
@interface TextInsertionUtils : NSObject
-+ (void)insertText:(NSString*)text at:(NSInteger)index additionalAttributes:(NSDictionary*)additionalAttrs input:(id)input withSelection:(BOOL)withSelection;
-+ (void)replaceText:(NSString*)text at:(NSRange)range additionalAttributes:(NSDictionary*)additionalAttrs input:(id)input withSelection:(BOOL)withSelection;;
-+ (void)insertTextInAttributedString:(NSString*)text at:(NSInteger)index additionalAttributes:(NSDictionary*)additionalAttrs attributedString:(NSMutableAttributedString *)attributedString;
++ (void)insertText:(NSString *)text
+ at:(NSInteger)index
+ additionalAttributes:
+ (NSDictionary *)additionalAttrs
+ input:(id)input
+ withSelection:(BOOL)withSelection;
++ (void)replaceText:(NSString *)text
+ at:(NSRange)range
+ additionalAttributes:
+ (NSDictionary *)additionalAttrs
+ input:(id)input
+ withSelection:(BOOL)withSelection;
+;
++ (void)insertTextInAttributedString:(NSString *)text
+ at:(NSInteger)index
+ additionalAttributes:
+ (NSDictionary *)additionalAttrs
+ attributedString:
+ (NSMutableAttributedString *)attributedString;
@end
diff --git a/ios/utils/TextInsertionUtils.mm b/ios/utils/TextInsertionUtils.mm
index 7a40a6023..87d3cb8fe 100644
--- a/ios/utils/TextInsertionUtils.mm
+++ b/ios/utils/TextInsertionUtils.mm
@@ -64,26 +64,28 @@ + (void)replaceText:(NSString *)text
typedInput->recentlyChangedRange = NSMakeRange(range.location, text.length);
}
-+ (void)insertTextInAttributedString:(NSString*)text
++ (void)insertTextInAttributedString:(NSString *)text
at:(NSInteger)index
- additionalAttributes:(NSDictionary*)additionalAttrs
- attributedString:(NSMutableAttributedString *)attributedString
-{
- NSDictionary *baseAttributes = @{};
- if (attributedString.length > 0 && index < attributedString.length) {
- baseAttributes = [attributedString attributesAtIndex:index effectiveRange:nil];
- }
+ additionalAttributes:
+ (NSDictionary *)additionalAttrs
+ attributedString:
+ (NSMutableAttributedString *)attributedString {
+ NSDictionary *baseAttributes = @{};
+ if (attributedString.length > 0 && index < attributedString.length) {
+ baseAttributes = [attributedString attributesAtIndex:index
+ effectiveRange:nil];
+ }
- NSMutableDictionary *attrs = [baseAttributes mutableCopy];
- if (additionalAttrs) {
- [attrs addEntriesFromDictionary:additionalAttrs];
- }
+ NSMutableDictionary *attrs = [baseAttributes mutableCopy];
+ if (additionalAttrs) {
+ [attrs addEntriesFromDictionary:additionalAttrs];
+ }
- NSAttributedString *newAttrString =
- [[NSAttributedString alloc] initWithString:text attributes:attrs];
+ NSAttributedString *newAttrString =
+ [[NSAttributedString alloc] initWithString:text attributes:attrs];
- // 4. Insert into the parent attributed string
- [attributedString insertAttributedString:newAttrString atIndex:index];
+ // 4. Insert into the parent attributed string
+ [attributedString insertAttributedString:newAttrString atIndex:index];
}
@end
From c5bbffea4ef2ab7ce755b5fd2dbbd26d2d29c663 Mon Sep 17 00:00:00 2001
From: IvanIhnatsiuk
Date: Thu, 11 Dec 2025 12:11:00 +0100
Subject: [PATCH 3/8] fix: properly handle text change notification
---
example/src/App.tsx | 12 +-
ios/EnrichedTextInputView.mm | 45 +-
ios/inputParser/AttributedStringBuilder.h | 12 +
ios/inputParser/AttributedStringBuilder.mm | 60 +
ios/inputParser/HtmlBuilder.h | 12 +
ios/inputParser/HtmlBuilder.mm | 484 ++++++++
ios/inputParser/HtmlTagInterpreter.h | 10 +
ios/inputParser/HtmlTagInterpreter.mm | 181 +++
ios/inputParser/HtmlTokenizer.h | 16 +
ios/inputParser/HtmlTokenizer.mm | 326 ++++++
ios/inputParser/InputParser.h | 3 +-
ios/inputParser/InputParser.mm | 1190 ++------------------
ios/inputParser/StyleSanitizer.h | 11 +
ios/inputParser/StyleSanitizer.mm | 63 ++
14 files changed, 1281 insertions(+), 1144 deletions(-)
create mode 100644 ios/inputParser/AttributedStringBuilder.h
create mode 100644 ios/inputParser/AttributedStringBuilder.mm
create mode 100644 ios/inputParser/HtmlBuilder.h
create mode 100644 ios/inputParser/HtmlBuilder.mm
create mode 100644 ios/inputParser/HtmlTagInterpreter.h
create mode 100644 ios/inputParser/HtmlTagInterpreter.mm
create mode 100644 ios/inputParser/HtmlTokenizer.h
create mode 100644 ios/inputParser/HtmlTokenizer.mm
create mode 100644 ios/inputParser/StyleSanitizer.h
create mode 100644 ios/inputParser/StyleSanitizer.mm
diff --git a/example/src/App.tsx b/example/src/App.tsx
index a4587fba7..9e6121c42 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -70,7 +70,7 @@ const DEBUG_SCROLLABLE = false;
// See: https://github.com/software-mansion/react-native-enriched/issues/229
const ANDROID_EXPERIMENTAL_SYNCHRONOUS_EVENTS = false;
-const generateHugeHtml = (repeat = 10) => {
+const generateHugeHtml = (repeat = 200) => {
const parts: string[] = [];
parts.push('');
@@ -106,11 +106,11 @@ const generateHugeHtml = (repeat = 10) => {
`\n `,
// Unordered list
- `\n`,
- `\nbullet A ${i} `,
- `\nbullet B ${i} `,
- `\nbullet C ${i} `,
- `\n `,
+ ``,
+ `bullet A ${i} `,
+ `bullet B ${i} `,
+ `bullet C ${i} `,
+ ` `,
// Ordered list
`\n`,
diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm
index 50104511b..0eb589d08 100644
--- a/ios/EnrichedTextInputView.mm
+++ b/ios/EnrichedTextInputView.mm
@@ -37,7 +37,6 @@ @implementation EnrichedTextInputView {
UILabel *_placeholderLabel;
UIColor *_placeholderColor;
BOOL _emitFocusBlur;
- BOOL _initialMount;
}
// MARK: - Component utils
@@ -555,6 +554,9 @@ - (void)updateProps:(Props::Shared const &)props
stylePropChanged = YES;
}
+ BOOL defaultValueChanged =
+ newViewProps.defaultValue != oldViewProps.defaultValue;
+
if (stylePropChanged) {
// all the text needs to be rebuilt
// we get the current html using old config, then switch to new config and
@@ -568,19 +570,22 @@ - (void)updateProps:(Props::Shared const &)props
// now set the new config
config = newConfig;
+ // we already applied html with styles in default value
+ if (!defaultValueChanged) {
+ // no emitting during styles reload
+ blockEmitting = YES;
+
+ // make sure everything is sound in the html
+ NSString *initiallyProcessedHtml =
+ [parser initiallyProcessHtml:currentHtml];
+ if (initiallyProcessedHtml != nullptr) {
+ [parser replaceWholeFromHtml:initiallyProcessedHtml
+ notifyAnyTextMayHaveBeenModified:!isFirstMount];
+ }
- // no emitting during styles reload
- blockEmitting = YES;
-
- // make sure everything is sound in the html
- NSString *initiallyProcessedHtml =
- [parser initiallyProcessHtml:currentHtml];
- if (initiallyProcessedHtml != nullptr) {
- [parser replaceWholeFromHtml:initiallyProcessedHtml];
+ blockEmitting = NO;
}
- blockEmitting = NO;
-
// fill the typing attributes with style props
defaultTypingAttributes[NSForegroundColorAttributeName] =
[config primaryColor];
@@ -604,7 +609,7 @@ - (void)updateProps:(Props::Shared const &)props
// default value - must be set before placeholder to make sure it correctly
// shows on first mount
- if (newViewProps.defaultValue != oldViewProps.defaultValue) {
+ if (defaultValueChanged) {
NSString *newDefaultValue =
[NSString fromCppString:newViewProps.defaultValue];
@@ -615,7 +620,8 @@ - (void)updateProps:(Props::Shared const &)props
textView.text = newDefaultValue;
} else {
// we've got some seemingly proper html
- [parser replaceWholeFromHtml:initiallyProcessedHtml];
+ [parser replaceWholeFromHtml:initiallyProcessedHtml
+ notifyAnyTextMayHaveBeenModified:!isFirstMount];
}
}
@@ -695,8 +701,13 @@ - (void)updateProps:(Props::Shared const &)props
_emitHtml = newViewProps.isOnChangeHtmlSet;
[super updateProps:props oldProps:oldProps];
- // run the changes callback
- [self anyTextMayHaveBeenModified];
+
+ // if default value changed it will be fired in default value update
+ // if this is initial mount it will be called in didMoveToWindow
+ if (!defaultValueChanged && !isFirstMount) {
+ // run the changes callback
+ [self anyTextMayHaveBeenModified];
+ }
// autofocus - needs to be done at the very end
if (isFirstMount && newViewProps.autoFocus) {
@@ -1028,7 +1039,8 @@ - (void)setValue:(NSString *)value {
textView.text = value;
} else {
// we've got some seemingly proper html
- [parser replaceWholeFromHtml:initiallyProcessedHtml];
+ [parser replaceWholeFromHtml:initiallyProcessedHtml
+ notifyAnyTextMayHaveBeenModified:YES];
}
// set recentlyChangedRange and check for changes
@@ -1445,6 +1457,7 @@ - (void)_performRelayout {
- (void)didMoveToWindow {
[super didMoveToWindow];
[self layoutIfNeeded];
+ [self anyTextMayHaveBeenModified];
}
// MARK: - UITextView delegate methods
diff --git a/ios/inputParser/AttributedStringBuilder.h b/ios/inputParser/AttributedStringBuilder.h
new file mode 100644
index 000000000..e4b39657e
--- /dev/null
+++ b/ios/inputParser/AttributedStringBuilder.h
@@ -0,0 +1,12 @@
+#import
+#import
+
+@interface AttributedStringBuilder : NSObject
+
+@property(nonatomic, weak) NSDictionary *stylesDict;
+
+- (void)apply:(NSArray *)processedStyles
+ toAttributedString:(NSMutableAttributedString *)attributedString
+ offsetFromBeginning:(NSInteger)offset;
+
+@end
diff --git a/ios/inputParser/AttributedStringBuilder.mm b/ios/inputParser/AttributedStringBuilder.mm
new file mode 100644
index 000000000..632f5a227
--- /dev/null
+++ b/ios/inputParser/AttributedStringBuilder.mm
@@ -0,0 +1,60 @@
+#import "AttributedStringBuilder.h"
+#import "StyleHeaders.h"
+#import "StylePair.h"
+
+@implementation AttributedStringBuilder
+
+- (void)apply:(NSArray *)processedStyles
+ toAttributedString:(NSMutableAttributedString *)attributedString
+ offsetFromBeginning:(NSInteger)offset {
+ NSArray *sorted = [processedStyles
+ sortedArrayUsingComparator:^NSComparisonResult(NSArray *a, NSArray *b) {
+ StylePair *pa = a[1];
+ StylePair *pb = b[1];
+
+ NSInteger la = offset + pa.rangeValue.rangeValue.location;
+ NSInteger lb = offset + pb.rangeValue.rangeValue.location;
+ return (la < lb ? NSOrderedDescending
+ : (la > lb ? NSOrderedAscending : NSOrderedSame));
+ }];
+
+ [attributedString beginEditing];
+
+ for (NSArray *arr in sorted) {
+ NSNumber *type = arr[0];
+ StylePair *pair = arr[1];
+
+ NSRange r = NSMakeRange(offset + pair.rangeValue.rangeValue.location,
+ pair.rangeValue.rangeValue.length);
+
+ id style = self.stylesDict[type];
+
+ if ([type isEqualToNumber:@([LinkStyle getStyleType])]) {
+ NSString *text = [attributedString.string substringWithRange:r];
+ NSString *url = pair.styleValue;
+ BOOL isManual = [text isEqualToString:url];
+ [(LinkStyle *)style addLinkInAttributedString:attributedString
+ range:r
+ text:text
+ url:url
+ manual:isManual];
+
+ } else if ([type isEqualToNumber:@([MentionStyle getStyleType])]) {
+ [(MentionStyle *)style addMentionInAttributedString:attributedString
+ range:r
+ params:pair.styleValue];
+
+ } else if ([type isEqualToNumber:@([ImageStyle getStyleType])]) {
+ [(ImageStyle *)style addImageInAttributedString:attributedString
+ range:r
+ imageData:pair.styleValue];
+
+ } else {
+ [style addAttributesInAttributedString:attributedString range:r];
+ }
+ }
+
+ [attributedString endEditing];
+}
+
+@end
diff --git a/ios/inputParser/HtmlBuilder.h b/ios/inputParser/HtmlBuilder.h
new file mode 100644
index 000000000..1ed77406d
--- /dev/null
+++ b/ios/inputParser/HtmlBuilder.h
@@ -0,0 +1,12 @@
+#import "EnrichedTextInputView.h"
+#import
+#import
+
+@interface HtmlBuilder : NSObject
+
+@property(nonatomic, weak) NSDictionary *stylesDict;
+@property(nonatomic, weak) EnrichedTextInputView *input;
+
+- (NSString *)htmlFromRange:(NSRange)range;
+
+@end
diff --git a/ios/inputParser/HtmlBuilder.mm b/ios/inputParser/HtmlBuilder.mm
new file mode 100644
index 000000000..e4608f028
--- /dev/null
+++ b/ios/inputParser/HtmlBuilder.mm
@@ -0,0 +1,484 @@
+#import "HtmlBuilder.h"
+#import "StringExtension.h"
+#import "StyleHeaders.h"
+
+@implementation HtmlBuilder
+
+- (NSString *)htmlFromRange:(NSRange)range {
+ NSInteger offset = range.location;
+ NSString *text =
+ [_input->textView.textStorage.string substringWithRange:range];
+
+ if (text.length == 0) {
+ return @"\n
\n";
+ }
+
+ NSMutableString *result = [[NSMutableString alloc] initWithString:@""];
+ NSSet *previousActiveStyles = [[NSSet alloc] init];
+ BOOL newLine = YES;
+ BOOL inUnorderedList = NO;
+ BOOL inOrderedList = NO;
+ BOOL inBlockQuote = NO;
+ BOOL inCodeBlock = NO;
+ unichar lastCharacter = 0;
+
+ for (int i = 0; i < text.length; i++) {
+ NSRange currentRange = NSMakeRange(offset + i, 1);
+ NSMutableSet *currentActiveStyles =
+ [[NSMutableSet alloc] init];
+ NSMutableDictionary *currentActiveStylesBeginning =
+ [[NSMutableDictionary alloc] init];
+
+ // check each existing style existence
+ for (NSNumber *type in _input->stylesDict) {
+ id style = _input->stylesDict[type];
+ if ([style detectStyle:currentRange]) {
+ [currentActiveStyles addObject:type];
+
+ if (![previousActiveStyles member:type]) {
+ currentActiveStylesBeginning[type] = [NSNumber numberWithInt:i];
+ }
+ } else if ([previousActiveStyles member:type]) {
+ [currentActiveStylesBeginning removeObjectForKey:type];
+ }
+ }
+
+ NSString *currentCharacterStr =
+ [_input->textView.textStorage.string substringWithRange:currentRange];
+ unichar currentCharacterChar = [_input->textView.textStorage.string
+ characterAtIndex:currentRange.location];
+
+ if ([[NSCharacterSet newlineCharacterSet]
+ characterIsMember:currentCharacterChar]) {
+ if (newLine) {
+ // we can either have an empty list item OR need to close the list and
+ // put a BR in such a situation the existence of the list must be
+ // 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)];
+ if (detected) {
+ [result appendString:@"\n "];
+ } else {
+ [result appendString:@"\n \n "];
+ inOrderedList = NO;
+ }
+ } else if (inUnorderedList) {
+ UnorderedListStyle *uStyle = _input->stylesDict[@(UnorderedList)];
+ BOOL detected =
+ [uStyle detectStyle:NSMakeRange(currentRange.location, 0)];
+ if (detected) {
+ [result appendString:@"\n "];
+ } else {
+ [result appendString:@"\n\n "];
+ inUnorderedList = NO;
+ }
+ } else {
+ [result appendString:@"\n "];
+ }
+ } else {
+ // newline finishes a paragraph and all style tags need to be closed
+ // we use previous styles
+ NSArray *sortedEndedStyles = [previousActiveStyles
+ sortedArrayUsingDescriptors:@[ [NSSortDescriptor
+ sortDescriptorWithKey:@"intValue"
+ ascending:NO] ]];
+
+ // append closing tags
+ for (NSNumber *style in sortedEndedStyles) {
+ if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
+ continue;
+ }
+ NSString *tagContent =
+ [self tagContentForStyle:style
+ openingTag:NO
+ location:currentRange.location];
+ [result
+ appendString:[NSString stringWithFormat:@"%@>", tagContent]];
+ }
+
+ // 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:@([BlockQuoteStyle getStyleType])] ||
+ [previousActiveStyles
+ containsObject:@([CodeBlockStyle getStyleType])]) {
+ // do nothing, proper closing paragraph tags have been already
+ // appended
+ } else {
+ [result appendString:@"
"];
+ }
+ }
+
+ // clear the previous styles
+ previousActiveStyles = [[NSSet alloc] init];
+
+ // next character opens new paragraph
+ newLine = YES;
+ } else {
+ // new line - open the paragraph
+ if (newLine) {
+ newLine = NO;
+
+ // handle ending unordered list
+ if (inUnorderedList &&
+ ![currentActiveStyles
+ containsObject:@([UnorderedListStyle getStyleType])]) {
+ inUnorderedList = NO;
+ [result appendString:@"\n"];
+ }
+ // handle ending ordered list
+ if (inOrderedList &&
+ ![currentActiveStyles
+ containsObject:@([OrderedListStyle getStyleType])]) {
+ inOrderedList = NO;
+ [result appendString:@"\n"];
+ }
+ // handle ending blockquotes
+ if (inBlockQuote &&
+ ![currentActiveStyles
+ containsObject:@([BlockQuoteStyle getStyleType])]) {
+ inBlockQuote = NO;
+ [result appendString:@"\n"];
+ }
+ // handle ending codeblock
+ if (inCodeBlock &&
+ ![currentActiveStyles
+ containsObject:@([CodeBlockStyle getStyleType])]) {
+ inCodeBlock = NO;
+ [result appendString:@"\n"];
+ }
+
+ // handle starting unordered list
+ if (!inUnorderedList &&
+ [currentActiveStyles
+ containsObject:@([UnorderedListStyle getStyleType])]) {
+ inUnorderedList = YES;
+ [result appendString:@"\n"];
+ }
+ // handle starting ordered list
+ if (!inOrderedList &&
+ [currentActiveStyles
+ containsObject:@([OrderedListStyle getStyleType])]) {
+ inOrderedList = YES;
+ [result appendString:@"\n"];
+ }
+ // handle starting blockquotes
+ if (!inBlockQuote &&
+ [currentActiveStyles
+ containsObject:@([BlockQuoteStyle getStyleType])]) {
+ inBlockQuote = YES;
+ [result appendString:@"\n"];
+ }
+ // handle starting codeblock
+ if (!inCodeBlock &&
+ [currentActiveStyles
+ containsObject:@([CodeBlockStyle getStyleType])]) {
+ inCodeBlock = 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:@([BlockQuoteStyle getStyleType])] ||
+ [currentActiveStyles
+ containsObject:@([CodeBlockStyle getStyleType])]) {
+ [result appendString:@"\n"];
+ } else {
+ [result appendString:@"\n
"];
+ }
+ }
+
+ // get styles that have ended
+ NSMutableSet *endedStyles =
+ [previousActiveStyles mutableCopy];
+ [endedStyles minusSet:currentActiveStyles];
+
+ // also finish styles that should be ended becasue they are nested in a
+ // style that ended
+ NSMutableSet *fixedEndedStyles = [endedStyles mutableCopy];
+ NSMutableSet *stylesToBeReAdded = [[NSMutableSet alloc] init];
+
+ for (NSNumber *style in endedStyles) {
+ NSInteger styleBeginning =
+ [currentActiveStylesBeginning[style] integerValue];
+
+ for (NSNumber *activeStyle in currentActiveStyles) {
+ NSInteger activeStyleBeginning =
+ [currentActiveStylesBeginning[activeStyle] integerValue];
+
+ // we end the styles that began after the currently ended style but
+ // not at the "i" (cause the old style ended at exactly "i-1" also the
+ // ones that began in the exact same place but are "inner" in relation
+ // to them due to StyleTypeEnum integer values
+
+ if ((activeStyleBeginning > styleBeginning &&
+ activeStyleBeginning < i) ||
+ (activeStyleBeginning == styleBeginning &&
+ activeStyleBeginning<
+ i && [activeStyle integerValue]>[style integerValue])) {
+ [fixedEndedStyles addObject:activeStyle];
+ [stylesToBeReAdded addObject:activeStyle];
+ }
+ }
+ }
+
+ // if a style begins but there is a style inner to it that is (and was
+ // previously) active, it also should be closed and readded
+
+ // newly added styles
+ NSMutableSet *newStyles = [currentActiveStyles mutableCopy];
+ [newStyles minusSet:previousActiveStyles];
+ // styles that were and still are active
+ NSMutableSet *stillActiveStyles = [previousActiveStyles mutableCopy];
+ [stillActiveStyles intersectSet:currentActiveStyles];
+
+ for (NSNumber *style in newStyles) {
+ for (NSNumber *ongoingStyle in stillActiveStyles) {
+ if ([ongoingStyle integerValue] > [style integerValue]) {
+ // the prev style is inner; needs to be closed and re-added later
+ [fixedEndedStyles addObject:ongoingStyle];
+ [stylesToBeReAdded addObject:ongoingStyle];
+ }
+ }
+ }
+
+ // they are sorted in a descending order
+ NSArray *sortedEndedStyles = [fixedEndedStyles
+ sortedArrayUsingDescriptors:@[ [NSSortDescriptor
+ sortDescriptorWithKey:@"intValue"
+ ascending:NO] ]];
+
+ // append closing tags
+ for (NSNumber *style in sortedEndedStyles) {
+ if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
+ continue;
+ }
+ NSString *tagContent = [self tagContentForStyle:style
+ openingTag:NO
+ location:currentRange.location];
+ [result appendString:[NSString stringWithFormat:@"%@>", tagContent]];
+ }
+
+ // all styles that have begun: new styles + the ones that need to be
+ // re-added they are sorted in a ascending manner to properly keep tags'
+ // FILO order
+ [newStyles unionSet:stylesToBeReAdded];
+ NSArray *sortedNewStyles = [newStyles
+ sortedArrayUsingDescriptors:@[ [NSSortDescriptor
+ sortDescriptorWithKey:@"intValue"
+ ascending:YES] ]];
+
+ // append opening tags
+ for (NSNumber *style in sortedNewStyles) {
+ NSString *tagContent = [self tagContentForStyle:style
+ openingTag:YES
+ location:currentRange.location];
+ if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
+ [result
+ appendString:[NSString stringWithFormat:@"<%@/>", tagContent]];
+ [currentActiveStyles removeObject:@([ImageStyle getStyleType])];
+ } else {
+ [result appendString:[NSString stringWithFormat:@"<%@>", tagContent]];
+ }
+ }
+
+ // append the letter and escape it if needed
+ [result appendString:[NSString stringByEscapingHtml:currentCharacterStr]];
+
+ // save current styles for next character's checks
+ previousActiveStyles = currentActiveStyles;
+ }
+
+ // set last character
+ lastCharacter = currentCharacterChar;
+ }
+
+ if (![[NSCharacterSet newlineCharacterSet] characterIsMember:lastCharacter]) {
+ // not-newline character was last - finish the paragraph
+ // close all pending tags
+ NSArray *sortedEndedStyles = [previousActiveStyles
+ sortedArrayUsingDescriptors:@[ [NSSortDescriptor
+ sortDescriptorWithKey:@"intValue"
+ ascending:NO] ]];
+
+ // append closing tags
+ for (NSNumber *style in sortedEndedStyles) {
+ if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
+ continue;
+ }
+ NSString *tagContent = [self
+ tagContentForStyle:style
+ openingTag:NO
+ location:_input->textView.textStorage.string.length - 1];
+ [result appendString:[NSString stringWithFormat:@"%@>", tagContent]];
+ }
+
+ // finish the paragraph
+ // handle ending of some paragraph styles
+ if ([previousActiveStyles
+ containsObject:@([UnorderedListStyle getStyleType])]) {
+ [result appendString:@"\n
"];
+ } else if ([previousActiveStyles
+ containsObject:@([OrderedListStyle getStyleType])]) {
+ [result appendString:@"\n"];
+ } else if ([previousActiveStyles
+ containsObject:@([BlockQuoteStyle getStyleType])]) {
+ [result appendString:@"\n"];
+ } else if ([previousActiveStyles
+ containsObject:@([CodeBlockStyle getStyleType])]) {
+ [result appendString:@"\n"];
+ } else if ([previousActiveStyles
+ containsObject:@([H1Style getStyleType])] ||
+ [previousActiveStyles
+ containsObject:@([H2Style getStyleType])] ||
+ [previousActiveStyles
+ containsObject:@([H3Style getStyleType])]) {
+ // do nothing, heading closing tag has already ben appended
+ } else {
+ [result appendString:@""];
+ }
+ } else {
+ // newline character was last - some paragraph styles need to be closed
+ if (inUnorderedList) {
+ inUnorderedList = NO;
+ [result appendString:@"\n"];
+ }
+ if (inOrderedList) {
+ inOrderedList = NO;
+ [result appendString:@"\n"];
+ }
+ if (inBlockQuote) {
+ inBlockQuote = NO;
+ [result appendString:@"\n"];
+ }
+ if (inCodeBlock) {
+ inCodeBlock = NO;
+ [result appendString:@"\n"];
+ }
+ }
+
+ [result appendString:@"\n"];
+
+ // remove zero width spaces in the very end
+ NSRange resultRange = NSMakeRange(0, result.length);
+ [result replaceOccurrencesOfString:@"\u200B"
+ withString:@""
+ options:0
+ range:resultRange];
+ return result;
+}
+
+- (NSString *)tagContentForStyle:(NSNumber *)style
+ openingTag:(BOOL)openingTag
+ location:(NSInteger)location {
+ if ([style isEqualToNumber:@([BoldStyle getStyleType])]) {
+ return @"b";
+ } else if ([style isEqualToNumber:@([ItalicStyle getStyleType])]) {
+ return @"i";
+ } else if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
+ if (openingTag) {
+ ImageStyle *imageStyle =
+ (ImageStyle *)_input->stylesDict[@([ImageStyle getStyleType])];
+ if (imageStyle != nullptr) {
+ ImageData *data = [imageStyle getImageDataAt:location];
+ if (data != nullptr && data.uri != nullptr) {
+ return [NSString
+ stringWithFormat:@"img src=\"%@\" width=\"%f\" height=\"%f\"",
+ data.uri, data.width, data.height];
+ }
+ }
+ return @"img";
+ } else {
+ return @"";
+ }
+ } else if ([style isEqualToNumber:@([UnderlineStyle getStyleType])]) {
+ return @"u";
+ } else if ([style isEqualToNumber:@([StrikethroughStyle getStyleType])]) {
+ return @"s";
+ } else if ([style isEqualToNumber:@([InlineCodeStyle getStyleType])]) {
+ return @"code";
+ } else if ([style isEqualToNumber:@([LinkStyle getStyleType])]) {
+ if (openingTag) {
+ LinkStyle *linkStyle =
+ (LinkStyle *)_input->stylesDict[@([LinkStyle getStyleType])];
+ if (linkStyle != nullptr) {
+ LinkData *data = [linkStyle getLinkDataAt:location];
+ if (data != nullptr && data.url != nullptr) {
+ return [NSString stringWithFormat:@"a href=\"%@\"", data.url];
+ }
+ }
+ return @"a";
+ } else {
+ return @"a";
+ }
+ } else if ([style isEqualToNumber:@([MentionStyle getStyleType])]) {
+ if (openingTag) {
+ MentionStyle *mentionStyle =
+ (MentionStyle *)_input->stylesDict[@([MentionStyle getStyleType])];
+ if (mentionStyle != nullptr) {
+ MentionParams *params = [mentionStyle getMentionParamsAt:location];
+ // attributes can theoretically be nullptr
+ if (params != nullptr && params.indicator != nullptr &&
+ params.text != nullptr) {
+ NSMutableString *attrsStr =
+ [[NSMutableString alloc] initWithString:@""];
+ if (params.attributes != nullptr) {
+ // turn attributes to Data and then into dict
+ NSData *attrsData =
+ [params.attributes dataUsingEncoding:NSUTF8StringEncoding];
+ NSError *jsonError;
+ NSDictionary *json =
+ [NSJSONSerialization JSONObjectWithData:attrsData
+ options:0
+ error:&jsonError];
+ // format dict keys and values into string
+ [json enumerateKeysAndObjectsUsingBlock:^(
+ id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {
+ [attrsStr
+ appendString:[NSString stringWithFormat:@" %@=\"%@\"",
+ (NSString *)key,
+ (NSString *)obj]];
+ }];
+ }
+ return [NSString
+ stringWithFormat:@"mention text=\"%@\" indicator=\"%@\"%@",
+ params.text, params.indicator, attrsStr];
+ }
+ }
+ return @"mention";
+ } else {
+ return @"mention";
+ }
+ } else if ([style isEqualToNumber:@([H1Style getStyleType])]) {
+ return @"h1";
+ } else if ([style isEqualToNumber:@([H2Style getStyleType])]) {
+ return @"h2";
+ } else if ([style isEqualToNumber:@([H3Style getStyleType])]) {
+ return @"h3";
+ } else if ([style isEqualToNumber:@([UnorderedListStyle getStyleType])] ||
+ [style isEqualToNumber:@([OrderedListStyle getStyleType])]) {
+ return @"li";
+ } else if ([style isEqualToNumber:@([BlockQuoteStyle getStyleType])] ||
+ [style isEqualToNumber:@([CodeBlockStyle getStyleType])]) {
+ // blockquotes and codeblock use tags the same way lists use
+ return @"p";
+ }
+ return @"";
+}
+
+@end
diff --git a/ios/inputParser/HtmlTagInterpreter.h b/ios/inputParser/HtmlTagInterpreter.h
new file mode 100644
index 000000000..3fdb825be
--- /dev/null
+++ b/ios/inputParser/HtmlTagInterpreter.h
@@ -0,0 +1,10 @@
+#import
+
+@class StylePair;
+
+@interface HtmlTagInterpreter : NSObject
+
+- (NSMutableArray *)convertTags:(NSArray *)initialTags
+ plainText:(NSString *)plainText;
+
+@end
diff --git a/ios/inputParser/HtmlTagInterpreter.mm b/ios/inputParser/HtmlTagInterpreter.mm
new file mode 100644
index 000000000..16d19b9ce
--- /dev/null
+++ b/ios/inputParser/HtmlTagInterpreter.mm
@@ -0,0 +1,181 @@
+#import "HtmlTagInterpreter.h"
+#import "ImageData.h"
+#import "MentionParams.h"
+#import "StyleHeaders.h"
+#import "StylePair.h"
+
+@implementation HtmlTagInterpreter
+
+- (NSMutableArray *)convertTags:(NSArray *)initiallyProcessedTags
+ plainText:(NSString *)plainText {
+ // process tags into proper StyleType + StylePair values
+ NSMutableArray *processedStyles = [[NSMutableArray alloc] init];
+
+ for (NSArray *arr in initiallyProcessedTags) {
+ NSString *tagName = (NSString *)arr[0];
+ NSValue *tagRangeValue = (NSValue *)arr[1];
+ NSMutableString *params = [[NSMutableString alloc] initWithString:@""];
+ if (arr.count > 2) {
+ [params appendString:(NSString *)arr[2]];
+ }
+
+ NSMutableArray *styleArr = [[NSMutableArray alloc] init];
+ StylePair *stylePair = [[StylePair alloc] init];
+ if ([tagName isEqualToString:@"b"]) {
+ [styleArr addObject:@([BoldStyle getStyleType])];
+ } else if ([tagName isEqualToString:@"i"]) {
+ [styleArr addObject:@([ItalicStyle getStyleType])];
+ } else if ([tagName isEqualToString:@"img"]) {
+ NSRegularExpression *srcRegex =
+ [NSRegularExpression regularExpressionWithPattern:@"src=\"([^\"]+)\""
+ options:0
+ error:nullptr];
+ NSTextCheckingResult *match =
+ [srcRegex firstMatchInString:params
+ options:0
+ range:NSMakeRange(0, params.length)];
+
+ if (match == nullptr) {
+ continue;
+ }
+
+ NSRange srcRange = match.range;
+ [styleArr addObject:@([ImageStyle getStyleType])];
+ // cut only the uri from the src="..." string
+ NSString *uri =
+ [params substringWithRange:NSMakeRange(srcRange.location + 5,
+ srcRange.length - 6)];
+ ImageData *imageData = [[ImageData alloc] init];
+ imageData.uri = uri;
+
+ NSRegularExpression *widthRegex = [NSRegularExpression
+ regularExpressionWithPattern:@"width=\"([0-9.]+)\""
+ options:0
+ error:nil];
+ NSTextCheckingResult *widthMatch =
+ [widthRegex firstMatchInString:params
+ options:0
+ range:NSMakeRange(0, params.length)];
+
+ if (widthMatch) {
+ NSString *widthString =
+ [params substringWithRange:[widthMatch rangeAtIndex:1]];
+ imageData.width = [widthString floatValue];
+ }
+
+ NSRegularExpression *heightRegex = [NSRegularExpression
+ regularExpressionWithPattern:@"height=\"([0-9.]+)\""
+ options:0
+ error:nil];
+ NSTextCheckingResult *heightMatch =
+ [heightRegex firstMatchInString:params
+ options:0
+ range:NSMakeRange(0, params.length)];
+
+ if (heightMatch) {
+ NSString *heightString =
+ [params substringWithRange:[heightMatch rangeAtIndex:1]];
+ imageData.height = [heightString floatValue];
+ }
+
+ stylePair.styleValue = imageData;
+ } else if ([tagName isEqualToString:@"u"]) {
+ [styleArr addObject:@([UnderlineStyle getStyleType])];
+ } else if ([tagName isEqualToString:@"s"]) {
+ [styleArr addObject:@([StrikethroughStyle getStyleType])];
+ } else if ([tagName isEqualToString:@"code"]) {
+ [styleArr addObject:@([InlineCodeStyle getStyleType])];
+ } else if ([tagName isEqualToString:@"a"]) {
+ NSRegularExpression *hrefRegex =
+ [NSRegularExpression regularExpressionWithPattern:@"href=\".+\""
+ options:0
+ error:nullptr];
+ NSTextCheckingResult *match =
+ [hrefRegex firstMatchInString:params
+ options:0
+ range:NSMakeRange(0, params.length)];
+
+ if (match == nullptr) {
+ // same as on Android, no href (or empty href) equals no link style
+ continue;
+ }
+
+ NSRange hrefRange = match.range;
+ [styleArr addObject:@([LinkStyle getStyleType])];
+ // cut only the url from the href="..." string
+ NSString *url =
+ [params substringWithRange:NSMakeRange(hrefRange.location + 6,
+ hrefRange.length - 7)];
+ stylePair.styleValue = url;
+ } else if ([tagName isEqualToString:@"mention"]) {
+ [styleArr addObject:@([MentionStyle getStyleType])];
+ // extract html expression into dict using some regex
+ NSMutableDictionary *paramsDict = [[NSMutableDictionary alloc] init];
+ NSString *pattern = @"(\\w+)=\"([^\"]*)\"";
+ NSRegularExpression *regex =
+ [NSRegularExpression regularExpressionWithPattern:pattern
+ options:0
+ error:nil];
+
+ [regex enumerateMatchesInString:params
+ options:0
+ range:NSMakeRange(0, params.length)
+ usingBlock:^(NSTextCheckingResult *_Nullable result,
+ NSMatchingFlags flags,
+ BOOL *_Nonnull stop) {
+ if (result.numberOfRanges == 3) {
+ NSString *key = [params
+ substringWithRange:[result rangeAtIndex:1]];
+ NSString *value = [params
+ substringWithRange:[result rangeAtIndex:2]];
+ paramsDict[key] = value;
+ }
+ }];
+
+ MentionParams *mentionParams = [[MentionParams alloc] init];
+ mentionParams.text = paramsDict[@"text"];
+ mentionParams.indicator = paramsDict[@"indicator"];
+
+ [paramsDict removeObjectsForKeys:@[ @"text", @"indicator" ]];
+ NSError *error;
+ NSData *attrsData = [NSJSONSerialization dataWithJSONObject:paramsDict
+ options:0
+ error:&error];
+ NSString *formattedAttrsString =
+ [[NSString alloc] initWithData:attrsData
+ encoding:NSUTF8StringEncoding];
+ mentionParams.attributes = formattedAttrsString;
+
+ stylePair.styleValue = mentionParams;
+ } else if ([[tagName substringWithRange:NSMakeRange(0, 1)]
+ isEqualToString:@"h"]) {
+ if ([tagName isEqualToString:@"h1"]) {
+ [styleArr addObject:@([H1Style getStyleType])];
+ } else if ([tagName isEqualToString:@"h2"]) {
+ [styleArr addObject:@([H2Style getStyleType])];
+ } else if ([tagName isEqualToString:@"h3"]) {
+ [styleArr addObject:@([H3Style getStyleType])];
+ }
+ } else if ([tagName isEqualToString:@"ul"]) {
+ [styleArr addObject:@([UnorderedListStyle getStyleType])];
+ } else if ([tagName isEqualToString:@"ol"]) {
+ [styleArr addObject:@([OrderedListStyle getStyleType])];
+ } else if ([tagName isEqualToString:@"blockquote"]) {
+ [styleArr addObject:@([BlockQuoteStyle getStyleType])];
+ } else if ([tagName isEqualToString:@"codeblock"]) {
+ [styleArr addObject:@([CodeBlockStyle getStyleType])];
+ } else {
+ // some other external tags like span just don't get put into the
+ // processed styles
+ continue;
+ }
+
+ stylePair.rangeValue = tagRangeValue;
+ [styleArr addObject:stylePair];
+ [processedStyles addObject:styleArr];
+ }
+
+ return processedStyles;
+}
+
+@end
diff --git a/ios/inputParser/HtmlTokenizer.h b/ios/inputParser/HtmlTokenizer.h
new file mode 100644
index 000000000..a25ea26b2
--- /dev/null
+++ b/ios/inputParser/HtmlTokenizer.h
@@ -0,0 +1,16 @@
+#import
+
+@interface HtmlTokenizationResult : NSObject
+@property(nonatomic, strong) NSString *plainText;
+@property(nonatomic, strong) NSMutableArray *initialTags;
+@end
+
+@interface HtmlTokenizer : NSObject
+- (NSString *)initiallyProcessHtml:(NSString *)html;
+- (NSString *)stringByAddingNewlinesToTag:(NSString *)tag
+ inString:(NSString *)html
+ leading:(BOOL)leading
+ trailing:(BOOL)trailing;
+
+- (HtmlTokenizationResult *)tokenize:(NSString *)fixedHtml;
+@end
diff --git a/ios/inputParser/HtmlTokenizer.mm b/ios/inputParser/HtmlTokenizer.mm
new file mode 100644
index 000000000..8b45141f6
--- /dev/null
+++ b/ios/inputParser/HtmlTokenizer.mm
@@ -0,0 +1,326 @@
+#import "HtmlTokenizer.h"
+#import "StringExtension.h"
+#import "StyleHeaders.h"
+
+@implementation HtmlTokenizationResult
+@end
+
+@implementation HtmlTokenizer
+
+static const int MIN_HTML_SIZE = 13;
+
+- (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html {
+ NSString *fixedHtml = nullptr;
+
+ if (html.length >= MIN_HTML_SIZE) {
+ NSString *firstSix = [html substringWithRange:NSMakeRange(0, 6)];
+ NSString *lastSeven =
+ [html substringWithRange:NSMakeRange(html.length - 7, 7)];
+
+ if ([firstSix isEqualToString:@""] &&
+ [lastSeven isEqualToString:@""]) {
+ // remove html tags, might be with newlines or without them
+ fixedHtml = [html copy];
+ NSRegularExpression *regex = [NSRegularExpression
+ regularExpressionWithPattern:@"\\n?|\\n?"
+ options:0
+ error:nil];
+
+ fixedHtml =
+ [regex stringByReplacingMatchesInString:html
+ options:0
+ range:NSMakeRange(0, html.length)
+ withTemplate:@""];
+ } else {
+ // in other case we are most likely working with some external html - try
+ // getting the styles from between body tags
+ NSRange openingBodyRange = [html rangeOfString:@""];
+ NSRange closingBodyRange = [html rangeOfString:@""];
+
+ if (openingBodyRange.length != 0 && closingBodyRange.length != 0) {
+ NSInteger newStart = openingBodyRange.location + 7;
+ NSInteger newEnd = closingBodyRange.location - 1;
+ fixedHtml = [html
+ substringWithRange:NSMakeRange(newStart, newEnd - newStart + 1)];
+ }
+ }
+ }
+
+ // second processing - try fixing htmls with wrong newlines' setup
+ if (fixedHtml) {
+ // add tag wherever needed
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"
"
+ withString:@" "];
+
+ // remove tags inside of
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@" "
+ withString:@"
"];
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@" "
+ withString:@" "];
+
+ // tags that have to be in separate lines
+ fixedHtml = [self stringByAddingNewlinesToTag:@" "
+ inString:fixedHtml
+ leading:YES
+ trailing:YES];
+ fixedHtml = [self stringByAddingNewlinesToTag:@""
+ inString:fixedHtml
+ leading:YES
+ trailing:YES];
+ fixedHtml = [self stringByAddingNewlinesToTag:@" "
+ inString:fixedHtml
+ leading:YES
+ trailing:YES];
+ fixedHtml = [self stringByAddingNewlinesToTag:@""
+ inString:fixedHtml
+ leading:YES
+ trailing:YES];
+ fixedHtml = [self stringByAddingNewlinesToTag:@" "
+ inString:fixedHtml
+ leading:YES
+ trailing:YES];
+ fixedHtml = [self stringByAddingNewlinesToTag:@""
+ inString:fixedHtml
+ leading:YES
+ trailing:YES];
+ fixedHtml = [self stringByAddingNewlinesToTag:@" "
+ inString:fixedHtml
+ leading:YES
+ trailing:YES];
+ fixedHtml = [self stringByAddingNewlinesToTag:@""
+ inString:fixedHtml
+ leading:YES
+ trailing:YES];
+ fixedHtml = [self stringByAddingNewlinesToTag:@" "
+ inString:fixedHtml
+ leading:YES
+ trailing:YES];
+
+ // line opening tags
+ fixedHtml = [self stringByAddingNewlinesToTag:@""
+ inString:fixedHtml
+ leading:YES
+ trailing:NO];
+ fixedHtml = [self stringByAddingNewlinesToTag:@"
"
+ inString:fixedHtml
+ leading:YES
+ trailing:NO];
+ fixedHtml = [self stringByAddingNewlinesToTag:@""
+ inString:fixedHtml
+ leading:YES
+ trailing:NO];
+ fixedHtml = [self stringByAddingNewlinesToTag:@""
+ inString:fixedHtml
+ leading:YES
+ trailing:NO];
+ fixedHtml = [self stringByAddingNewlinesToTag:@""
+ inString:fixedHtml
+ leading:YES
+ trailing:NO];
+
+ // line closing tags
+ fixedHtml = [self stringByAddingNewlinesToTag:@""
+ inString:fixedHtml
+ leading:NO
+ trailing:YES];
+ fixedHtml = [self stringByAddingNewlinesToTag:@" "
+ inString:fixedHtml
+ leading:NO
+ trailing:YES];
+ fixedHtml = [self stringByAddingNewlinesToTag:@""
+ inString:fixedHtml
+ leading:NO
+ trailing:YES];
+ fixedHtml = [self stringByAddingNewlinesToTag:@""
+ inString:fixedHtml
+ leading:NO
+ trailing:YES];
+ fixedHtml = [self stringByAddingNewlinesToTag:@""
+ inString:fixedHtml
+ leading:NO
+ trailing:YES];
+ }
+
+ return fixedHtml;
+}
+
+- (NSString *)stringByAddingNewlinesToTag:(NSString *)tag
+ inString:(NSString *)html
+ leading:(BOOL)leading
+ trailing:(BOOL)trailing {
+ NSString *str = [html copy];
+ if (leading) {
+ NSString *formattedTag = [NSString stringWithFormat:@">%@", tag];
+ NSString *formattedNewTag = [NSString stringWithFormat:@">\n%@", tag];
+ str = [str stringByReplacingOccurrencesOfString:formattedTag
+ withString:formattedNewTag];
+ }
+ if (trailing) {
+ NSString *formattedTag = [NSString stringWithFormat:@"%@<", tag];
+ NSString *formattedNewTag = [NSString stringWithFormat:@"%@\n<", tag];
+ str = [str stringByReplacingOccurrencesOfString:formattedTag
+ withString:formattedNewTag];
+ }
+ return str;
+}
+
+- (HtmlTokenizationResult *)tokenize:(NSString *)fixedHtml {
+ NSMutableString *plainText = [[NSMutableString alloc] initWithString:@""];
+ NSMutableDictionary *ongoingTags = [[NSMutableDictionary alloc] init];
+ NSMutableArray *initiallyProcessedTags = [[NSMutableArray alloc] init];
+ BOOL insideTag = NO;
+ BOOL gettingTagName = NO;
+ BOOL gettingTagParams = NO;
+ BOOL closingTag = NO;
+ NSMutableString *currentTagName =
+ [[NSMutableString alloc] initWithString:@""];
+ NSMutableString *currentTagParams =
+ [[NSMutableString alloc] initWithString:@""];
+ NSDictionary *htmlEntitiesDict =
+ [NSString getEscapedCharactersInfoFrom:fixedHtml];
+
+ // firstly, extract text and initially processed tags
+ for (int i = 0; i < fixedHtml.length; i++) {
+ NSString *currentCharacterStr =
+ [fixedHtml substringWithRange:NSMakeRange(i, 1)];
+ unichar currentCharacterChar = [fixedHtml characterAtIndex:i];
+
+ if (currentCharacterChar == '<') {
+ // opening the tag, mark that we are inside and getting its name
+ insideTag = YES;
+ gettingTagName = YES;
+ } else if (currentCharacterChar == '>') {
+ // finishing some tag, no longer marked as inside or getting its
+ // name/params
+ insideTag = NO;
+ gettingTagName = NO;
+ gettingTagParams = NO;
+
+ BOOL isSelfClosing = NO;
+
+ // Check if params ended with '/' (e.g. )
+ if ([currentTagParams hasSuffix:@"/"]) {
+ [currentTagParams
+ deleteCharactersInRange:NSMakeRange(currentTagParams.length - 1,
+ 1)];
+ isSelfClosing = YES;
+ }
+
+ if ([currentTagName isEqualToString:@"p"] ||
+ [currentTagName isEqualToString:@"br"] ||
+ [currentTagName isEqualToString:@"li"]) {
+ // do nothing, we don't include these tags in styles
+ } else if (!closingTag) {
+ // we finish opening tag - get its location and optionally params and
+ // put them under tag name key in ongoingTags
+ NSMutableArray *tagArr = [[NSMutableArray alloc] init];
+ [tagArr addObject:[NSNumber numberWithInteger:plainText.length]];
+ if (currentTagParams.length > 0) {
+ [tagArr addObject:[currentTagParams copy]];
+ }
+ ongoingTags[currentTagName] = tagArr;
+
+ // skip one newline after opening tags that are in separate lines
+ // intentionally
+ if ([currentTagName isEqualToString:@"ul"] ||
+ [currentTagName isEqualToString:@"ol"] ||
+ [currentTagName isEqualToString:@"blockquote"] ||
+ [currentTagName isEqualToString:@"codeblock"]) {
+ i += 1;
+ }
+
+ if (isSelfClosing) {
+ [self finalizeTag:currentTagName
+ ongoingTags:ongoingTags
+ initiallyProcessedTags:initiallyProcessedTags
+ plainText:plainText];
+ }
+ } else {
+ // we finish closing tags - pack tag name, tag range and optionally tag
+ // params into an entry that goes inside initiallyProcessedTags
+
+ // skip one newline that was added before some closing tags that are in
+ // separate lines
+ if ([currentTagName isEqualToString:@"ul"] ||
+ [currentTagName isEqualToString:@"ol"] ||
+ [currentTagName isEqualToString:@"blockquote"] ||
+ [currentTagName isEqualToString:@"codeblock"]) {
+ plainText = [[plainText
+ substringWithRange:NSMakeRange(0, plainText.length - 1)]
+ mutableCopy];
+ }
+
+ [self finalizeTag:currentTagName
+ ongoingTags:ongoingTags
+ initiallyProcessedTags:initiallyProcessedTags
+ plainText:plainText];
+ }
+ // post-tag cleanup
+ closingTag = NO;
+ currentTagName = [[NSMutableString alloc] initWithString:@""];
+ currentTagParams = [[NSMutableString alloc] initWithString:@""];
+ } else {
+ if (!insideTag) {
+ // no tags logic - just append the right text
+
+ // html entity on the index; use unescaped character and forward
+ // iterator accordingly
+ NSArray *entityInfo = htmlEntitiesDict[@(i)];
+ if (entityInfo != nullptr) {
+ NSString *escaped = entityInfo[0];
+ NSString *unescaped = entityInfo[1];
+ [plainText appendString:unescaped];
+ // the iterator will forward by 1 itself
+ i += escaped.length - 1;
+ } else {
+ [plainText appendString:currentCharacterStr];
+ }
+ } else {
+ if (gettingTagName) {
+ if (currentCharacterChar == ' ') {
+ // no longer getting tag name - switch to params
+ gettingTagName = NO;
+ gettingTagParams = YES;
+ } else if (currentCharacterChar == '/') {
+ // mark that the tag is closing
+ closingTag = YES;
+ } else {
+ // append next tag char
+ [currentTagName appendString:currentCharacterStr];
+ }
+ } else if (gettingTagParams) {
+ // append next tag params char
+ [currentTagParams appendString:currentCharacterStr];
+ }
+ }
+ }
+ }
+
+ HtmlTokenizationResult *result = [[HtmlTokenizationResult alloc] init];
+ result.plainText = plainText;
+ result.initialTags = initiallyProcessedTags;
+
+ return result;
+}
+
+- (void)finalizeTag:(NSMutableString *)tagName
+ ongoingTags:(NSMutableDictionary *)ongoingTags
+ initiallyProcessedTags:(NSMutableArray *)processedTags
+ plainText:(NSMutableString *)plainText {
+ NSMutableArray *tagEntry = [[NSMutableArray alloc] init];
+
+ NSArray *tagData = ongoingTags[tagName];
+ NSInteger tagLocation = [((NSNumber *)tagData[0]) intValue];
+ NSRange tagRange = NSMakeRange(tagLocation, plainText.length - tagLocation);
+
+ [tagEntry addObject:[tagName copy]];
+ [tagEntry addObject:[NSValue valueWithRange:tagRange]];
+ if (tagData.count > 1) {
+ [tagEntry addObject:[(NSString *)tagData[1] copy]];
+ }
+
+ [processedTags addObject:tagEntry];
+ [ongoingTags removeObjectForKey:tagName];
+}
+
+@end
diff --git a/ios/inputParser/InputParser.h b/ios/inputParser/InputParser.h
index b54ace32e..5b922abc9 100644
--- a/ios/inputParser/InputParser.h
+++ b/ios/inputParser/InputParser.h
@@ -4,7 +4,8 @@
@interface InputParser : NSObject
- (instancetype _Nonnull)initWithInput:(id _Nonnull)input;
- (NSString *_Nonnull)parseToHtmlFromRange:(NSRange)range;
-- (void)replaceWholeFromHtml:(NSString *_Nonnull)html;
+- (void)replaceWholeFromHtml:(NSString *_Nonnull)html
+ notifyAnyTextMayHaveBeenModified:(BOOL)notifyAnyTextMayHaveBeenModified;
- (void)replaceFromHtml:(NSString *_Nonnull)html range:(NSRange)range;
- (void)insertFromHtml:(NSString *_Nonnull)html location:(NSInteger)location;
- (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html;
diff --git a/ios/inputParser/InputParser.mm b/ios/inputParser/InputParser.mm
index 43b9d1f17..b73e78636 100644
--- a/ios/inputParser/InputParser.mm
+++ b/ios/inputParser/InputParser.mm
@@ -1,673 +1,122 @@
#import "InputParser.h"
#import "EnrichedTextInputView.h"
-#import "StringExtension.h"
+
+#import "AttributedStringBuilder.h"
+#import "HtmlBuilder.h"
+#import "HtmlTagInterpreter.h"
+#import "HtmlTokenizer.h"
+#import "StyleSanitizer.h"
+
#import "StyleHeaders.h"
-#import "TextInsertionUtils.h"
-#import "UIView+React.h"
@implementation InputParser {
EnrichedTextInputView *_input;
+ HtmlTokenizer *_tokenizer;
+ HtmlTagInterpreter *_interpreter;
+ StyleSanitizer *_sanitizer;
+ AttributedStringBuilder *_builder;
+ HtmlBuilder *_htmlBuilder;
}
- (instancetype)initWithInput:(id)input {
self = [super init];
_input = (EnrichedTextInputView *)input;
- return self;
-}
-
-- (NSString *)parseToHtmlFromRange:(NSRange)range {
- NSInteger offset = range.location;
- NSString *text =
- [_input->textView.textStorage.string substringWithRange:range];
-
- if (text.length == 0) {
- return @"\n
\n";
- }
-
- NSMutableString *result = [[NSMutableString alloc] initWithString:@""];
- NSSet *previousActiveStyles = [[NSSet alloc] init];
- BOOL newLine = YES;
- BOOL inUnorderedList = NO;
- BOOL inOrderedList = NO;
- BOOL inBlockQuote = NO;
- BOOL inCodeBlock = NO;
- unichar lastCharacter = 0;
-
- for (int i = 0; i < text.length; i++) {
- NSRange currentRange = NSMakeRange(offset + i, 1);
- NSMutableSet *currentActiveStyles =
- [[NSMutableSet alloc] init];
- NSMutableDictionary *currentActiveStylesBeginning =
- [[NSMutableDictionary alloc] init];
-
- // check each existing style existence
- for (NSNumber *type in _input->stylesDict) {
- id style = _input->stylesDict[type];
- if ([style detectStyle:currentRange]) {
- [currentActiveStyles addObject:type];
-
- if (![previousActiveStyles member:type]) {
- currentActiveStylesBeginning[type] = [NSNumber numberWithInt:i];
- }
- } else if ([previousActiveStyles member:type]) {
- [currentActiveStylesBeginning removeObjectForKey:type];
- }
- }
-
- NSString *currentCharacterStr =
- [_input->textView.textStorage.string substringWithRange:currentRange];
- unichar currentCharacterChar = [_input->textView.textStorage.string
- characterAtIndex:currentRange.location];
-
- if ([[NSCharacterSet newlineCharacterSet]
- characterIsMember:currentCharacterChar]) {
- if (newLine) {
- // we can either have an empty list item OR need to close the list and
- // put a BR in such a situation the existence of the list must be
- // 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)];
- if (detected) {
- [result appendString:@"\n "];
- } else {
- [result appendString:@"\n\n "];
- inOrderedList = NO;
- }
- } else if (inUnorderedList) {
- UnorderedListStyle *uStyle = _input->stylesDict[@(UnorderedList)];
- BOOL detected =
- [uStyle detectStyle:NSMakeRange(currentRange.location, 0)];
- if (detected) {
- [result appendString:@"\n "];
- } else {
- [result appendString:@"\n\n "];
- inUnorderedList = NO;
- }
- } else {
- [result appendString:@"\n "];
- }
- } else {
- // newline finishes a paragraph and all style tags need to be closed
- // we use previous styles
- NSArray *sortedEndedStyles = [previousActiveStyles
- sortedArrayUsingDescriptors:@[ [NSSortDescriptor
- sortDescriptorWithKey:@"intValue"
- ascending:NO] ]];
-
- // append closing tags
- for (NSNumber *style in sortedEndedStyles) {
- if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
- continue;
- }
- NSString *tagContent =
- [self tagContentForStyle:style
- openingTag:NO
- location:currentRange.location];
- [result
- appendString:[NSString stringWithFormat:@"%@>", tagContent]];
- }
-
- // 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:@([BlockQuoteStyle getStyleType])] ||
- [previousActiveStyles
- containsObject:@([CodeBlockStyle getStyleType])]) {
- // do nothing, proper closing paragraph tags have been already
- // appended
- } else {
- [result appendString:@""];
- }
- }
-
- // clear the previous styles
- previousActiveStyles = [[NSSet alloc] init];
-
- // next character opens new paragraph
- newLine = YES;
- } else {
- // new line - open the paragraph
- if (newLine) {
- newLine = NO;
-
- // handle ending unordered list
- if (inUnorderedList &&
- ![currentActiveStyles
- containsObject:@([UnorderedListStyle getStyleType])]) {
- inUnorderedList = NO;
- [result appendString:@"\n"];
- }
- // handle ending ordered list
- if (inOrderedList &&
- ![currentActiveStyles
- containsObject:@([OrderedListStyle getStyleType])]) {
- inOrderedList = NO;
- [result appendString:@"\n"];
- }
- // handle ending blockquotes
- if (inBlockQuote &&
- ![currentActiveStyles
- containsObject:@([BlockQuoteStyle getStyleType])]) {
- inBlockQuote = NO;
- [result appendString:@"\n"];
- }
- // handle ending codeblock
- if (inCodeBlock &&
- ![currentActiveStyles
- containsObject:@([CodeBlockStyle getStyleType])]) {
- inCodeBlock = NO;
- [result appendString:@"\n"];
- }
-
- // handle starting unordered list
- if (!inUnorderedList &&
- [currentActiveStyles
- containsObject:@([UnorderedListStyle getStyleType])]) {
- inUnorderedList = YES;
- [result appendString:@"\n"];
- }
- // handle starting ordered list
- if (!inOrderedList &&
- [currentActiveStyles
- containsObject:@([OrderedListStyle getStyleType])]) {
- inOrderedList = YES;
- [result appendString:@"\n"];
- }
- // handle starting blockquotes
- if (!inBlockQuote &&
- [currentActiveStyles
- containsObject:@([BlockQuoteStyle getStyleType])]) {
- inBlockQuote = YES;
- [result appendString:@"\n"];
- }
- // handle starting codeblock
- if (!inCodeBlock &&
- [currentActiveStyles
- containsObject:@([CodeBlockStyle getStyleType])]) {
- inCodeBlock = 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:@([BlockQuoteStyle getStyleType])] ||
- [currentActiveStyles
- containsObject:@([CodeBlockStyle getStyleType])]) {
- [result appendString:@"\n"];
- } else {
- [result appendString:@"\n
"];
- }
- }
-
- // get styles that have ended
- NSMutableSet *endedStyles =
- [previousActiveStyles mutableCopy];
- [endedStyles minusSet:currentActiveStyles];
-
- // also finish styles that should be ended becasue they are nested in a
- // style that ended
- NSMutableSet *fixedEndedStyles = [endedStyles mutableCopy];
- NSMutableSet *stylesToBeReAdded = [[NSMutableSet alloc] init];
-
- for (NSNumber *style in endedStyles) {
- NSInteger styleBeginning =
- [currentActiveStylesBeginning[style] integerValue];
-
- for (NSNumber *activeStyle in currentActiveStyles) {
- NSInteger activeStyleBeginning =
- [currentActiveStylesBeginning[activeStyle] integerValue];
-
- // we end the styles that began after the currently ended style but
- // not at the "i" (cause the old style ended at exactly "i-1" also the
- // ones that began in the exact same place but are "inner" in relation
- // to them due to StyleTypeEnum integer values
-
- if ((activeStyleBeginning > styleBeginning &&
- activeStyleBeginning < i) ||
- (activeStyleBeginning == styleBeginning &&
- activeStyleBeginning<
- i && [activeStyle integerValue]>[style integerValue])) {
- [fixedEndedStyles addObject:activeStyle];
- [stylesToBeReAdded addObject:activeStyle];
- }
- }
- }
-
- // if a style begins but there is a style inner to it that is (and was
- // previously) active, it also should be closed and readded
-
- // newly added styles
- NSMutableSet *newStyles = [currentActiveStyles mutableCopy];
- [newStyles minusSet:previousActiveStyles];
- // styles that were and still are active
- NSMutableSet *stillActiveStyles = [previousActiveStyles mutableCopy];
- [stillActiveStyles intersectSet:currentActiveStyles];
-
- for (NSNumber *style in newStyles) {
- for (NSNumber *ongoingStyle in stillActiveStyles) {
- if ([ongoingStyle integerValue] > [style integerValue]) {
- // the prev style is inner; needs to be closed and re-added later
- [fixedEndedStyles addObject:ongoingStyle];
- [stylesToBeReAdded addObject:ongoingStyle];
- }
- }
- }
-
- // they are sorted in a descending order
- NSArray *sortedEndedStyles = [fixedEndedStyles
- sortedArrayUsingDescriptors:@[ [NSSortDescriptor
- sortDescriptorWithKey:@"intValue"
- ascending:NO] ]];
-
- // append closing tags
- for (NSNumber *style in sortedEndedStyles) {
- if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
- continue;
- }
- NSString *tagContent = [self tagContentForStyle:style
- openingTag:NO
- location:currentRange.location];
- [result appendString:[NSString stringWithFormat:@"%@>", tagContent]];
- }
-
- // all styles that have begun: new styles + the ones that need to be
- // re-added they are sorted in a ascending manner to properly keep tags'
- // FILO order
- [newStyles unionSet:stylesToBeReAdded];
- NSArray *sortedNewStyles = [newStyles
- sortedArrayUsingDescriptors:@[ [NSSortDescriptor
- sortDescriptorWithKey:@"intValue"
- ascending:YES] ]];
-
- // append opening tags
- for (NSNumber *style in sortedNewStyles) {
- NSString *tagContent = [self tagContentForStyle:style
- openingTag:YES
- location:currentRange.location];
- if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
- [result
- appendString:[NSString stringWithFormat:@"<%@/>", tagContent]];
- [currentActiveStyles removeObject:@([ImageStyle getStyleType])];
- } else {
- [result appendString:[NSString stringWithFormat:@"<%@>", tagContent]];
- }
- }
- // append the letter and escape it if needed
- [result appendString:[NSString stringByEscapingHtml:currentCharacterStr]];
+ _tokenizer = [HtmlTokenizer new];
+ _interpreter = [HtmlTagInterpreter new];
+ _sanitizer = [StyleSanitizer new];
- // save current styles for next character's checks
- previousActiveStyles = currentActiveStyles;
- }
+ _builder = [AttributedStringBuilder new];
+ _builder.stylesDict = _input->stylesDict;
- // set last character
- lastCharacter = currentCharacterChar;
- }
+ _htmlBuilder = [HtmlBuilder new];
+ _htmlBuilder.stylesDict = _input->stylesDict;
+ _htmlBuilder.input = _input;
- if (![[NSCharacterSet newlineCharacterSet] characterIsMember:lastCharacter]) {
- // not-newline character was last - finish the paragraph
- // close all pending tags
- NSArray *sortedEndedStyles = [previousActiveStyles
- sortedArrayUsingDescriptors:@[ [NSSortDescriptor
- sortDescriptorWithKey:@"intValue"
- ascending:NO] ]];
+ return self;
+}
- // append closing tags
- for (NSNumber *style in sortedEndedStyles) {
- if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
- continue;
- }
- NSString *tagContent = [self
- tagContentForStyle:style
- openingTag:NO
- location:_input->textView.textStorage.string.length - 1];
- [result appendString:[NSString stringWithFormat:@"%@>", tagContent]];
- }
+- (NSArray *)getTextAndStylesFromHtml:(NSString *)html {
+ if (!html)
+ return @[ @"", @[] ];
+ HtmlTokenizationResult *tokens = [_tokenizer tokenize:html];
- // finish the paragraph
- // handle ending of some paragraph styles
- if ([previousActiveStyles
- containsObject:@([UnorderedListStyle getStyleType])]) {
- [result appendString:@"\n
"];
- } else if ([previousActiveStyles
- containsObject:@([OrderedListStyle getStyleType])]) {
- [result appendString:@"\n"];
- } else if ([previousActiveStyles
- containsObject:@([BlockQuoteStyle getStyleType])]) {
- [result appendString:@"\n"];
- } else if ([previousActiveStyles
- containsObject:@([CodeBlockStyle getStyleType])]) {
- [result appendString:@"\n"];
- } else if ([previousActiveStyles
- containsObject:@([H1Style getStyleType])] ||
- [previousActiveStyles
- containsObject:@([H2Style getStyleType])] ||
- [previousActiveStyles
- containsObject:@([H3Style getStyleType])]) {
- // do nothing, heading closing tag has already ben appended
- } else {
- [result appendString:@""];
- }
- } else {
- // newline character was last - some paragraph styles need to be closed
- if (inUnorderedList) {
- inUnorderedList = NO;
- [result appendString:@"\n"];
- }
- if (inOrderedList) {
- inOrderedList = NO;
- [result appendString:@"\n"];
- }
- if (inBlockQuote) {
- inBlockQuote = NO;
- [result appendString:@"\n"];
- }
- if (inCodeBlock) {
- inCodeBlock = NO;
- [result appendString:@"\n"];
- }
- }
+ NSMutableArray *processed = [_interpreter convertTags:tokens.initialTags
+ plainText:tokens.plainText];
- [result appendString:@"\n"];
+ [_sanitizer sanitizeStyles:processed
+ blocking:_input.blockingStyles
+ conflicting:_input.conflictingStyles];
- // remove zero width spaces in the very end
- NSRange resultRange = NSMakeRange(0, result.length);
- [result replaceOccurrencesOfString:@"\u200B"
- withString:@""
- options:0
- range:resultRange];
- return result;
+ return @[ tokens.plainText, processed ];
}
-- (NSString *)tagContentForStyle:(NSNumber *)style
- openingTag:(BOOL)openingTag
- location:(NSInteger)location {
- if ([style isEqualToNumber:@([BoldStyle getStyleType])]) {
- return @"b";
- } else if ([style isEqualToNumber:@([ItalicStyle getStyleType])]) {
- return @"i";
- } else if ([style isEqualToNumber:@([ImageStyle getStyleType])]) {
- if (openingTag) {
- ImageStyle *imageStyle =
- (ImageStyle *)_input->stylesDict[@([ImageStyle getStyleType])];
- if (imageStyle != nullptr) {
- ImageData *data = [imageStyle getImageDataAt:location];
- if (data != nullptr && data.uri != nullptr) {
- return [NSString
- stringWithFormat:@"img src=\"%@\" width=\"%f\" height=\"%f\"",
- data.uri, data.width, data.height];
- }
- }
- return @"img";
- } else {
- return @"";
- }
- } else if ([style isEqualToNumber:@([UnderlineStyle getStyleType])]) {
- return @"u";
- } else if ([style isEqualToNumber:@([StrikethroughStyle getStyleType])]) {
- return @"s";
- } else if ([style isEqualToNumber:@([InlineCodeStyle getStyleType])]) {
- return @"code";
- } else if ([style isEqualToNumber:@([LinkStyle getStyleType])]) {
- if (openingTag) {
- LinkStyle *linkStyle =
- (LinkStyle *)_input->stylesDict[@([LinkStyle getStyleType])];
- if (linkStyle != nullptr) {
- LinkData *data = [linkStyle getLinkDataAt:location];
- if (data != nullptr && data.url != nullptr) {
- return [NSString stringWithFormat:@"a href=\"%@\"", data.url];
- }
- }
- return @"a";
- } else {
- return @"a";
- }
- } else if ([style isEqualToNumber:@([MentionStyle getStyleType])]) {
- if (openingTag) {
- MentionStyle *mentionStyle =
- (MentionStyle *)_input->stylesDict[@([MentionStyle getStyleType])];
- if (mentionStyle != nullptr) {
- MentionParams *params = [mentionStyle getMentionParamsAt:location];
- // attributes can theoretically be nullptr
- if (params != nullptr && params.indicator != nullptr &&
- params.text != nullptr) {
- NSMutableString *attrsStr =
- [[NSMutableString alloc] initWithString:@""];
- if (params.attributes != nullptr) {
- // turn attributes to Data and then into dict
- NSData *attrsData =
- [params.attributes dataUsingEncoding:NSUTF8StringEncoding];
- NSError *jsonError;
- NSDictionary *json =
- [NSJSONSerialization JSONObjectWithData:attrsData
- options:0
- error:&jsonError];
- // format dict keys and values into string
- [json enumerateKeysAndObjectsUsingBlock:^(
- id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {
- [attrsStr
- appendString:[NSString stringWithFormat:@" %@=\"%@\"",
- (NSString *)key,
- (NSString *)obj]];
- }];
- }
- return [NSString
- stringWithFormat:@"mention text=\"%@\" indicator=\"%@\"%@",
- params.text, params.indicator, attrsStr];
- }
- }
- return @"mention";
- } else {
- return @"mention";
- }
- } else if ([style isEqualToNumber:@([H1Style getStyleType])]) {
- return @"h1";
- } else if ([style isEqualToNumber:@([H2Style getStyleType])]) {
- return @"h2";
- } else if ([style isEqualToNumber:@([H3Style getStyleType])]) {
- return @"h3";
- } else if ([style isEqualToNumber:@([UnorderedListStyle getStyleType])] ||
- [style isEqualToNumber:@([OrderedListStyle getStyleType])]) {
- return @"li";
- } else if ([style isEqualToNumber:@([BlockQuoteStyle getStyleType])] ||
- [style isEqualToNumber:@([CodeBlockStyle getStyleType])]) {
- // blockquotes and codeblock use tags the same way lists use
- return @"p";
- }
- return @"";
-}
+- (void)replaceWholeFromHtml:(NSString *)html
+ notifyAnyTextMayHaveBeenModified:(BOOL)notifyAnyTextMayHaveBeenModified {
+ NSArray *parsed = [self getTextAndStylesFromHtml:html];
+ NSString *plain = parsed[0];
+ NSArray *styles = parsed[1];
-- (void)replaceWholeFromHtml:(NSString *_Nonnull)html {
- NSArray *processingResult = [self getTextAndStylesFromHtml:html];
- NSString *plainText = (NSString *)processingResult[0];
- NSArray *stylesInfo = (NSArray *)processingResult[1];
+ NSLog(@"replace whole from html %@", html);
- NSMutableAttributedString *newAttr = [[NSMutableAttributedString alloc]
- initWithString:plainText
- attributes:_input->defaultTypingAttributes];
+ NSMutableAttributedString *attributedString =
+ [[NSMutableAttributedString alloc]
+ initWithString:plain
+ attributes:_input->defaultTypingAttributes];
- [self applyProcessedStyles:stylesInfo
- toAttributedString:newAttr
- offsetFromBeginning:0];
+ [_builder apply:styles
+ toAttributedString:attributedString
+ offsetFromBeginning:0];
NSTextStorage *storage = _input->textView.textStorage;
-
- [storage setAttributedString:newAttr];
+ [storage setAttributedString:attributedString];
_input->textView.typingAttributes = _input->defaultTypingAttributes;
- [_input anyTextMayHaveBeenModified];
-}
-
-- (BOOL)styleType:(NSNumber *)type
- existsInStyles:(NSArray *)styles
- atRange:(NSRange)r {
- for (NSArray *entry in styles) {
- NSNumber *otherType = entry[0];
- StylePair *pair = entry[1];
- if ([otherType isEqualToNumber:type] &&
- NSEqualRanges(pair.rangeValue.rangeValue, r)) {
- return YES;
- }
- }
- return NO;
-}
-
-- (void)removeStyleType:(NSNumber *)type
- fromStyles:(NSMutableArray *)styles
- atRange:(NSRange)r {
- NSMutableArray *remove = [NSMutableArray array];
-
- for (NSArray *entry in styles) {
- NSNumber *otherType = entry[0];
- StylePair *pair = entry[1];
-
- if ([otherType isEqualToNumber:type] &&
- NSEqualRanges(pair.rangeValue.rangeValue, r)) {
- [remove addObject:entry];
- }
+ if (notifyAnyTextMayHaveBeenModified) {
+ [_input anyTextMayHaveBeenModified];
}
-
- [styles removeObjectsInArray:remove];
-}
-
-- (BOOL)shouldApplyStyle:(StyleType)styleType
- existingStyles:(NSArray *)styles
- at:(NSRange)range {
- NSArray *blocking = _input->blockingStyles[@(styleType)];
- for (NSNumber *b in blocking) {
- if ([self styleType:b existsInStyles:styles atRange:range]) {
- return NO;
- }
- }
-
- NSArray *conflicting = _input->conflictingStyles[@(styleType)];
- for (NSNumber *c in conflicting) {
- [self removeStyleType:c fromStyles:styles atRange:range];
- }
-
- return YES;
-}
-
-- (void)applyProcessedStyles:(NSArray *)processedStyles
- toAttributedString:(NSMutableAttributedString *)attributedString
- offsetFromBeginning:(NSInteger)offset {
- NSArray *sorted = [processedStyles
- sortedArrayUsingComparator:^NSComparisonResult(NSArray *a, NSArray *b) {
- StylePair *pa = a[1];
- StylePair *pb = b[1];
-
- NSInteger la = offset + pa.rangeValue.rangeValue.location;
- NSInteger lb = offset + pb.rangeValue.rangeValue.location;
-
- if (la > lb)
- return NSOrderedAscending;
- if (la < lb)
- return NSOrderedDescending;
- return NSOrderedSame;
- }];
-
- [attributedString beginEditing];
-
- for (NSArray *arr in sorted) {
- NSNumber *styleType = arr[0];
- StylePair *stylePair = arr[1];
- id style = _input->stylesDict[styleType];
-
- NSRange r = NSMakeRange(offset + stylePair.rangeValue.rangeValue.location,
- stylePair.rangeValue.rangeValue.length);
- if ([styleType isEqualToNumber:@([LinkStyle getStyleType])]) {
- NSString *text = [attributedString.string substringWithRange:r];
- NSString *url = stylePair.styleValue;
- BOOL isManual = [text isEqualToString:url];
- [(LinkStyle *)style addLinkInAttributedString:attributedString
- range:r
- text:text
- url:url
- manual:isManual];
-
- } else if ([styleType isEqualToNumber:@([MentionStyle getStyleType])]) {
- [(MentionStyle *)style addMentionInAttributedString:attributedString
- range:r
- params:stylePair.styleValue];
- } else if ([styleType isEqualToNumber:@([ImageStyle getStyleType])]) {
- [(ImageStyle *)style addImageInAttributedString:attributedString
- range:r
- imageData:stylePair.styleValue];
- } else {
- [style addAttributesInAttributedString:attributedString range:r];
- }
- }
-
- [attributedString endEditing];
}
-- (void)replaceFromHtml:(NSString *_Nonnull)html range:(NSRange)range {
- if (!html || range.length == 0)
- return;
- NSArray *processingResult = [self getTextAndStylesFromHtml:html];
- if (processingResult.count < 2)
- return;
-
- NSString *plainText = processingResult[0];
- NSArray *stylesInfo = processingResult[1];
+- (void)replaceFromHtml:(NSString *)html range:(NSRange)range {
+ NSArray *parsed = [self getTextAndStylesFromHtml:html];
+ NSString *plainText = parsed[0];
+ NSArray *styles = parsed[1];
NSMutableAttributedString *inserted = [[NSMutableAttributedString alloc]
initWithString:plainText
attributes:_input->defaultTypingAttributes];
- [self applyProcessedStyles:stylesInfo
- toAttributedString:inserted
- offsetFromBeginning:0];
+
+ [_builder apply:styles toAttributedString:inserted offsetFromBeginning:0];
NSTextStorage *storage = _input->textView.textStorage;
if (range.location > storage.length)
range.location = storage.length;
-
- if (NSMaxRange(range) > storage.length)
+ if (NSMaxRange(range) > storage.length) {
range.length = storage.length - range.location;
+ }
[storage beginEditing];
[storage replaceCharactersInRange:range withAttributedString:inserted];
[storage endEditing];
+
_input->textView.selectedRange =
NSMakeRange(range.location + inserted.length, 0);
-
_input->textView.typingAttributes = _input->defaultTypingAttributes;
-
[_input anyTextMayHaveBeenModified];
}
-- (void)insertFromHtml:(NSString *_Nonnull)html location:(NSInteger)location {
- NSArray *processingResult = [self getTextAndStylesFromHtml:html];
- NSString *plainText = processingResult[0];
- NSArray *stylesInfo = processingResult[1];
+- (void)insertFromHtml:(NSString *)html location:(NSInteger)location {
+
+ NSArray *parsed = [self getTextAndStylesFromHtml:html];
+ NSString *plain = parsed[0];
+ NSArray *styles = parsed[1];
- // Base attributed segment for inserted text
NSMutableAttributedString *inserted = [[NSMutableAttributedString alloc]
- initWithString:plainText
+ initWithString:plain
attributes:_input->defaultTypingAttributes];
- [self applyProcessedStyles:stylesInfo
- toAttributedString:inserted
- offsetFromBeginning:0];
- if (location > _input->textView.textStorage.length) {
- location = _input->textView.textStorage.length;
- }
+ [_builder apply:styles toAttributedString:inserted offsetFromBeginning:0];
[_input->textView.textStorage beginEditing];
[_input->textView.textStorage insertAttributedString:inserted
@@ -677,515 +126,14 @@ - (void)insertFromHtml:(NSString *_Nonnull)html location:(NSInteger)location {
_input->textView.selectedRange = NSMakeRange(location + inserted.length, 0);
}
-- (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html {
- NSString *fixedHtml = nullptr;
-
- if (html.length >= 13) {
- NSString *firstSix = [html substringWithRange:NSMakeRange(0, 6)];
- NSString *lastSeven =
- [html substringWithRange:NSMakeRange(html.length - 7, 7)];
-
- if ([firstSix isEqualToString:@""] &&
- [lastSeven isEqualToString:@""]) {
- // remove html tags, might be with newlines or without them
- fixedHtml = [html copy];
- // firstly remove newlined html tags if any:
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"\n"
- withString:@""];
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"\n"
- withString:@""];
- // fallback; remove html tags without their newlines
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@""
- withString:@""];
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@""
- withString:@""];
- } else {
- // in other case we are most likely working with some external html - try
- // getting the styles from between body tags
- NSRange openingBodyRange = [html rangeOfString:@""];
- NSRange closingBodyRange = [html rangeOfString:@""];
-
- if (openingBodyRange.length != 0 && closingBodyRange.length != 0) {
- NSInteger newStart = openingBodyRange.location + 7;
- NSInteger newEnd = closingBodyRange.location - 1;
- fixedHtml = [html
- substringWithRange:NSMakeRange(newStart, newEnd - newStart + 1)];
- }
- }
- }
-
- // second processing - try fixing htmls with wrong newlines' setup
- if (fixedHtml != nullptr) {
- // add tag wherever needed
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"
"
- withString:@" "];
-
- // remove tags inside of
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@" "
- withString:@"
"];
- fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@" "
- withString:@" "];
-
- // tags that have to be in separate lines
- fixedHtml = [self stringByAddingNewlinesToTag:@" "
- inString:fixedHtml
- leading:YES
- trailing:YES];
- fixedHtml = [self stringByAddingNewlinesToTag:@""
- inString:fixedHtml
- leading:YES
- trailing:YES];
- fixedHtml = [self stringByAddingNewlinesToTag:@" "
- inString:fixedHtml
- leading:YES
- trailing:YES];
- fixedHtml = [self stringByAddingNewlinesToTag:@""
- inString:fixedHtml
- leading:YES
- trailing:YES];
- fixedHtml = [self stringByAddingNewlinesToTag:@" "
- inString:fixedHtml
- leading:YES
- trailing:YES];
- fixedHtml = [self stringByAddingNewlinesToTag:@""
- inString:fixedHtml
- leading:YES
- trailing:YES];
- fixedHtml = [self stringByAddingNewlinesToTag:@" "
- inString:fixedHtml
- leading:YES
- trailing:YES];
- fixedHtml = [self stringByAddingNewlinesToTag:@""
- inString:fixedHtml
- leading:YES
- trailing:YES];
- fixedHtml = [self stringByAddingNewlinesToTag:@" "
- inString:fixedHtml
- leading:YES
- trailing:YES];
-
- // line opening tags
- fixedHtml = [self stringByAddingNewlinesToTag:@""
- inString:fixedHtml
- leading:YES
- trailing:NO];
- fixedHtml = [self stringByAddingNewlinesToTag:@"
"
- inString:fixedHtml
- leading:YES
- trailing:NO];
- fixedHtml = [self stringByAddingNewlinesToTag:@""
- inString:fixedHtml
- leading:YES
- trailing:NO];
- fixedHtml = [self stringByAddingNewlinesToTag:@""
- inString:fixedHtml
- leading:YES
- trailing:NO];
- fixedHtml = [self stringByAddingNewlinesToTag:@""
- inString:fixedHtml
- leading:YES
- trailing:NO];
-
- // line closing tags
- fixedHtml = [self stringByAddingNewlinesToTag:@""
- inString:fixedHtml
- leading:NO
- trailing:YES];
- fixedHtml = [self stringByAddingNewlinesToTag:@" "
- inString:fixedHtml
- leading:NO
- trailing:YES];
- fixedHtml = [self stringByAddingNewlinesToTag:@""
- inString:fixedHtml
- leading:NO
- trailing:YES];
- fixedHtml = [self stringByAddingNewlinesToTag:@""
- inString:fixedHtml
- leading:NO
- trailing:YES];
- fixedHtml = [self stringByAddingNewlinesToTag:@""
- inString:fixedHtml
- leading:NO
- trailing:YES];
- }
-
- return fixedHtml;
-}
-
-- (NSString *)stringByAddingNewlinesToTag:(NSString *)tag
- inString:(NSString *)html
- leading:(BOOL)leading
- trailing:(BOOL)trailing {
- NSString *str = [html copy];
- if (leading) {
- NSString *formattedTag = [NSString stringWithFormat:@">%@", tag];
- NSString *formattedNewTag = [NSString stringWithFormat:@">\n%@", tag];
- str = [str stringByReplacingOccurrencesOfString:formattedTag
- withString:formattedNewTag];
- }
- if (trailing) {
- NSString *formattedTag = [NSString stringWithFormat:@"%@<", tag];
- NSString *formattedNewTag = [NSString stringWithFormat:@"%@\n<", tag];
- str = [str stringByReplacingOccurrencesOfString:formattedTag
- withString:formattedNewTag];
- }
- return str;
-}
-
-- (void)finalizeTagEntry:(NSMutableString *)tagName
- ongoingTags:(NSMutableDictionary *)ongoingTags
- initiallyProcessedTags:(NSMutableArray *)processedTags
- plainText:(NSMutableString *)plainText {
- NSMutableArray *tagEntry = [[NSMutableArray alloc] init];
-
- NSArray *tagData = ongoingTags[tagName];
- NSInteger tagLocation = [((NSNumber *)tagData[0]) intValue];
- NSRange tagRange = NSMakeRange(tagLocation, plainText.length - tagLocation);
-
- [tagEntry addObject:[tagName copy]];
- [tagEntry addObject:[NSValue valueWithRange:tagRange]];
- if (tagData.count > 1) {
- [tagEntry addObject:[(NSString *)tagData[1] copy]];
- }
-
- [processedTags addObject:tagEntry];
- [ongoingTags removeObjectForKey:tagName];
-}
-
-- (void)sanitizeStyles:(NSMutableArray *)styles {
- NSMutableArray *toRemove = [NSMutableArray array];
- for (NSArray *entry in [styles copy]) {
- NSNumber *styleType = entry[0];
- StylePair *pair = entry[1];
- NSRange r = pair.rangeValue.rangeValue;
-
- BOOL shouldRemove = NO;
- NSArray *blocking = _input.blockingStyles[styleType];
- for (NSNumber *bType in blocking) {
- if ([self styleType:bType existsInStyles:styles atRange:r]) {
- shouldRemove = YES;
- break;
- }
- }
-
- if (shouldRemove) {
- [toRemove addObject:entry];
- continue;
- }
-
- NSArray *conflicting = _input.conflictingStyles[styleType];
- for (NSNumber *cType in conflicting) {
- [self removeStyleType:cType fromStyles:styles atRange:r];
- }
-
- if (shouldRemove) {
- [toRemove addObject:entry];
- }
- }
-
- [styles removeObjectsInArray:toRemove];
+- (NSString *)initiallyProcessHtml:(NSString *)html {
+ return [_tokenizer initiallyProcessHtml:html];
}
-- (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml {
- NSMutableString *plainText = [[NSMutableString alloc] initWithString:@""];
- NSMutableDictionary *ongoingTags = [[NSMutableDictionary alloc] init];
- NSMutableArray *initiallyProcessedTags = [[NSMutableArray alloc] init];
- BOOL insideTag = NO;
- BOOL gettingTagName = NO;
- BOOL gettingTagParams = NO;
- BOOL closingTag = NO;
- NSMutableString *currentTagName =
- [[NSMutableString alloc] initWithString:@""];
- NSMutableString *currentTagParams =
- [[NSMutableString alloc] initWithString:@""];
- NSDictionary *htmlEntitiesDict =
- [NSString getEscapedCharactersInfoFrom:fixedHtml];
-
- // firstly, extract text and initially processed tags
- for (int i = 0; i < fixedHtml.length; i++) {
- NSString *currentCharacterStr =
- [fixedHtml substringWithRange:NSMakeRange(i, 1)];
- unichar currentCharacterChar = [fixedHtml characterAtIndex:i];
-
- if (currentCharacterChar == '<') {
- // opening the tag, mark that we are inside and getting its name
- insideTag = YES;
- gettingTagName = YES;
- } else if (currentCharacterChar == '>') {
- // finishing some tag, no longer marked as inside or getting its
- // name/params
- insideTag = NO;
- gettingTagName = NO;
- gettingTagParams = NO;
-
- BOOL isSelfClosing = NO;
-
- // Check if params ended with '/' (e.g. )
- if ([currentTagParams hasSuffix:@"/"]) {
- [currentTagParams
- deleteCharactersInRange:NSMakeRange(currentTagParams.length - 1,
- 1)];
- isSelfClosing = YES;
- }
-
- if ([currentTagName isEqualToString:@"p"] ||
- [currentTagName isEqualToString:@"br"] ||
- [currentTagName isEqualToString:@"li"]) {
- // do nothing, we don't include these tags in styles
- } else if (!closingTag) {
- // we finish opening tag - get its location and optionally params and
- // put them under tag name key in ongoingTags
- NSMutableArray *tagArr = [[NSMutableArray alloc] init];
- [tagArr addObject:[NSNumber numberWithInteger:plainText.length]];
- if (currentTagParams.length > 0) {
- [tagArr addObject:[currentTagParams copy]];
- }
- ongoingTags[currentTagName] = tagArr;
-
- // skip one newline after opening tags that are in separate lines
- // intentionally
- if ([currentTagName isEqualToString:@"ul"] ||
- [currentTagName isEqualToString:@"ol"] ||
- [currentTagName isEqualToString:@"blockquote"] ||
- [currentTagName isEqualToString:@"codeblock"]) {
- i += 1;
- }
-
- if (isSelfClosing) {
- [self finalizeTagEntry:currentTagName
- ongoingTags:ongoingTags
- initiallyProcessedTags:initiallyProcessedTags
- plainText:plainText];
- }
- } else {
- // we finish closing tags - pack tag name, tag range and optionally tag
- // params into an entry that goes inside initiallyProcessedTags
-
- // skip one newline that was added before some closing tags that are in
- // separate lines
- if ([currentTagName isEqualToString:@"ul"] ||
- [currentTagName isEqualToString:@"ol"] ||
- [currentTagName isEqualToString:@"blockquote"] ||
- [currentTagName isEqualToString:@"codeblock"]) {
- plainText = [[plainText
- substringWithRange:NSMakeRange(0, plainText.length - 1)]
- mutableCopy];
- }
-
- [self finalizeTagEntry:currentTagName
- ongoingTags:ongoingTags
- initiallyProcessedTags:initiallyProcessedTags
- plainText:plainText];
- }
- // post-tag cleanup
- closingTag = NO;
- currentTagName = [[NSMutableString alloc] initWithString:@""];
- currentTagParams = [[NSMutableString alloc] initWithString:@""];
- } else {
- if (!insideTag) {
- // no tags logic - just append the right text
-
- // html entity on the index; use unescaped character and forward
- // iterator accordingly
- NSArray *entityInfo = htmlEntitiesDict[@(i)];
- if (entityInfo != nullptr) {
- NSString *escaped = entityInfo[0];
- NSString *unescaped = entityInfo[1];
- [plainText appendString:unescaped];
- // the iterator will forward by 1 itself
- i += escaped.length - 1;
- } else {
- [plainText appendString:currentCharacterStr];
- }
- } else {
- if (gettingTagName) {
- if (currentCharacterChar == ' ') {
- // no longer getting tag name - switch to params
- gettingTagName = NO;
- gettingTagParams = YES;
- } else if (currentCharacterChar == '/') {
- // mark that the tag is closing
- closingTag = YES;
- } else {
- // append next tag char
- [currentTagName appendString:currentCharacterStr];
- }
- } else if (gettingTagParams) {
- // append next tag params char
- [currentTagParams appendString:currentCharacterStr];
- }
- }
- }
- }
-
- // process tags into proper StyleType + StylePair values
- NSMutableArray *processedStyles = [[NSMutableArray alloc] init];
+#pragma mark - NSAttributedString → HTML
- for (NSArray *arr in initiallyProcessedTags) {
- NSString *tagName = (NSString *)arr[0];
- NSValue *tagRangeValue = (NSValue *)arr[1];
- NSMutableString *params = [[NSMutableString alloc] initWithString:@""];
- if (arr.count > 2) {
- [params appendString:(NSString *)arr[2]];
- }
-
- NSMutableArray *styleArr = [[NSMutableArray alloc] init];
- StylePair *stylePair = [[StylePair alloc] init];
- if ([tagName isEqualToString:@"b"]) {
- [styleArr addObject:@([BoldStyle getStyleType])];
- } else if ([tagName isEqualToString:@"i"]) {
- [styleArr addObject:@([ItalicStyle getStyleType])];
- } else if ([tagName isEqualToString:@"img"]) {
- NSRegularExpression *srcRegex =
- [NSRegularExpression regularExpressionWithPattern:@"src=\"([^\"]+)\""
- options:0
- error:nullptr];
- NSTextCheckingResult *match =
- [srcRegex firstMatchInString:params
- options:0
- range:NSMakeRange(0, params.length)];
-
- if (match == nullptr) {
- continue;
- }
-
- NSRange srcRange = match.range;
- [styleArr addObject:@([ImageStyle getStyleType])];
- // cut only the uri from the src="..." string
- NSString *uri =
- [params substringWithRange:NSMakeRange(srcRange.location + 5,
- srcRange.length - 6)];
- ImageData *imageData = [[ImageData alloc] init];
- imageData.uri = uri;
-
- NSRegularExpression *widthRegex = [NSRegularExpression
- regularExpressionWithPattern:@"width=\"([0-9.]+)\""
- options:0
- error:nil];
- NSTextCheckingResult *widthMatch =
- [widthRegex firstMatchInString:params
- options:0
- range:NSMakeRange(0, params.length)];
-
- if (widthMatch) {
- NSString *widthString =
- [params substringWithRange:[widthMatch rangeAtIndex:1]];
- imageData.width = [widthString floatValue];
- }
-
- NSRegularExpression *heightRegex = [NSRegularExpression
- regularExpressionWithPattern:@"height=\"([0-9.]+)\""
- options:0
- error:nil];
- NSTextCheckingResult *heightMatch =
- [heightRegex firstMatchInString:params
- options:0
- range:NSMakeRange(0, params.length)];
-
- if (heightMatch) {
- NSString *heightString =
- [params substringWithRange:[heightMatch rangeAtIndex:1]];
- imageData.height = [heightString floatValue];
- }
-
- stylePair.styleValue = imageData;
- } else if ([tagName isEqualToString:@"u"]) {
- [styleArr addObject:@([UnderlineStyle getStyleType])];
- } else if ([tagName isEqualToString:@"s"]) {
- [styleArr addObject:@([StrikethroughStyle getStyleType])];
- } else if ([tagName isEqualToString:@"code"]) {
- [styleArr addObject:@([InlineCodeStyle getStyleType])];
- } else if ([tagName isEqualToString:@"a"]) {
- NSRegularExpression *hrefRegex =
- [NSRegularExpression regularExpressionWithPattern:@"href=\".+\""
- options:0
- error:nullptr];
- NSTextCheckingResult *match =
- [hrefRegex firstMatchInString:params
- options:0
- range:NSMakeRange(0, params.length)];
-
- if (match == nullptr) {
- // same as on Android, no href (or empty href) equals no link style
- continue;
- }
-
- NSRange hrefRange = match.range;
- [styleArr addObject:@([LinkStyle getStyleType])];
- // cut only the url from the href="..." string
- NSString *url =
- [params substringWithRange:NSMakeRange(hrefRange.location + 6,
- hrefRange.length - 7)];
- stylePair.styleValue = url;
- } else if ([tagName isEqualToString:@"mention"]) {
- [styleArr addObject:@([MentionStyle getStyleType])];
- // extract html expression into dict using some regex
- NSMutableDictionary *paramsDict = [[NSMutableDictionary alloc] init];
- NSString *pattern = @"(\\w+)=\"([^\"]*)\"";
- NSRegularExpression *regex =
- [NSRegularExpression regularExpressionWithPattern:pattern
- options:0
- error:nil];
-
- [regex enumerateMatchesInString:params
- options:0
- range:NSMakeRange(0, params.length)
- usingBlock:^(NSTextCheckingResult *_Nullable result,
- NSMatchingFlags flags,
- BOOL *_Nonnull stop) {
- if (result.numberOfRanges == 3) {
- NSString *key = [params
- substringWithRange:[result rangeAtIndex:1]];
- NSString *value = [params
- substringWithRange:[result rangeAtIndex:2]];
- paramsDict[key] = value;
- }
- }];
-
- MentionParams *mentionParams = [[MentionParams alloc] init];
- mentionParams.text = paramsDict[@"text"];
- mentionParams.indicator = paramsDict[@"indicator"];
-
- [paramsDict removeObjectsForKeys:@[ @"text", @"indicator" ]];
- NSError *error;
- NSData *attrsData = [NSJSONSerialization dataWithJSONObject:paramsDict
- options:0
- error:&error];
- NSString *formattedAttrsString =
- [[NSString alloc] initWithData:attrsData
- encoding:NSUTF8StringEncoding];
- mentionParams.attributes = formattedAttrsString;
-
- stylePair.styleValue = mentionParams;
- } else if ([[tagName substringWithRange:NSMakeRange(0, 1)]
- isEqualToString:@"h"]) {
- if ([tagName isEqualToString:@"h1"]) {
- [styleArr addObject:@([H1Style getStyleType])];
- } else if ([tagName isEqualToString:@"h2"]) {
- [styleArr addObject:@([H2Style getStyleType])];
- } else if ([tagName isEqualToString:@"h3"]) {
- [styleArr addObject:@([H3Style getStyleType])];
- }
- } else if ([tagName isEqualToString:@"ul"]) {
- [styleArr addObject:@([UnorderedListStyle getStyleType])];
- } else if ([tagName isEqualToString:@"ol"]) {
- [styleArr addObject:@([OrderedListStyle getStyleType])];
- } else if ([tagName isEqualToString:@"blockquote"]) {
- [styleArr addObject:@([BlockQuoteStyle getStyleType])];
- } else if ([tagName isEqualToString:@"codeblock"]) {
- [styleArr addObject:@([CodeBlockStyle getStyleType])];
- } else {
- // some other external tags like span just don't get put into the
- // processed styles
- continue;
- }
-
- stylePair.rangeValue = tagRangeValue;
- [styleArr addObject:stylePair];
- [processedStyles addObject:styleArr];
- }
- [self sanitizeStyles:processedStyles];
- return @[ plainText, processedStyles ];
+- (NSString *)parseToHtmlFromRange:(NSRange)range {
+ return [_htmlBuilder htmlFromRange:range];
}
@end
diff --git a/ios/inputParser/StyleSanitizer.h b/ios/inputParser/StyleSanitizer.h
new file mode 100644
index 000000000..0625f1876
--- /dev/null
+++ b/ios/inputParser/StyleSanitizer.h
@@ -0,0 +1,11 @@
+#import
+
+@interface StyleSanitizer : NSObject
+
+- (void)sanitizeStyles:(NSMutableArray *)styles
+ blocking:
+ (NSDictionary *> *)blocking
+ conflicting:
+ (NSDictionary *> *)conflicting;
+
+@end
diff --git a/ios/inputParser/StyleSanitizer.mm b/ios/inputParser/StyleSanitizer.mm
new file mode 100644
index 000000000..86cbaab44
--- /dev/null
+++ b/ios/inputParser/StyleSanitizer.mm
@@ -0,0 +1,63 @@
+#import "StyleSanitizer.h"
+#import "StylePair.h"
+
+@implementation StyleSanitizer
+
+- (void)sanitizeStyles:(NSMutableArray *)styles
+ blocking:
+ (NSDictionary *> *)blocking
+ conflicting:
+ (NSDictionary *> *)conflicting {
+ NSMutableDictionary *> *rangeToTypes =
+ [NSMutableDictionary new];
+
+ for (NSArray *entry in styles) {
+ NSNumber *type = entry[0];
+ NSValue *rKey = ((StylePair *)entry[1]).rangeValue;
+
+ if (!rangeToTypes[rKey]) {
+ rangeToTypes[rKey] = [NSMutableSet new];
+ }
+ [rangeToTypes[rKey] addObject:type];
+ }
+
+ for (NSValue *rKey in rangeToTypes) {
+ NSMutableSet *types = rangeToTypes[rKey];
+
+ for (NSNumber *type in [types copy]) {
+
+ // BLOCKING: remove style if blocked
+ for (NSNumber *b in blocking[type]) {
+ if ([types containsObject:b]) {
+ [types removeObject:type];
+ break;
+ }
+ }
+ if (![types containsObject:type])
+ continue;
+
+ for (NSNumber *c in conflicting[type]) {
+ if ([types containsObject:c]) {
+ [types removeObject:c];
+ }
+ }
+ }
+ }
+
+ NSMutableArray *final = [NSMutableArray new];
+
+ for (NSArray *entry in styles) {
+ NSNumber *type = entry[0];
+ StylePair *pair = entry[1];
+
+ NSMutableSet *set = rangeToTypes[pair.rangeValue];
+ if ([set containsObject:type]) {
+ [final addObject:@[ type, pair ]];
+ }
+ }
+
+ [styles removeAllObjects];
+ [styles addObjectsFromArray:final];
+}
+
+@end
From c31d6de1be309779a864db79892b0b1058a4ace4 Mon Sep 17 00:00:00 2001
From: IvanIhnatsiuk
Date: Thu, 11 Dec 2025 21:38:34 +0100
Subject: [PATCH 4/8] fix: properly set default attributed string
---
example/src/App.tsx | 72 -------
ios/EnrichedTextInputView.mm | 10 +-
ios/inputParser/AttributedStringBuilder.h | 4 +-
ios/inputParser/AttributedStringBuilder.mm | 66 ++++---
.../ConvertHtmlToPlainTextAndStylesResult.h | 7 +
.../ConvertHtmlToPlainTextAndStylesResult.mm | 10 +
ios/inputParser/HtmlHandler.h | 10 +
.../{HtmlTokenizer.mm => HtmlHandler.mm} | 80 +++++++-
ios/inputParser/HtmlTagInterpreter.h | 10 -
ios/inputParser/HtmlTagInterpreter.mm | 181 ------------------
ios/inputParser/HtmlTokenizationResult.h | 8 +
ios/inputParser/HtmlTokenizationResult.mm | 11 ++
ios/inputParser/HtmlTokenizer.h | 16 --
ios/inputParser/InputParser.h | 2 +
ios/inputParser/InputParser.mm | 70 +++----
ios/inputParser/StyleSanitizer.h | 11 --
ios/inputParser/StyleSanitizer.mm | 63 ------
ios/inputParser/TagHandlersFactory.h | 8 +
ios/inputParser/TagHandlersFactory.mm | 163 ++++++++++++++++
ios/styles/BlockQuoteStyle.mm | 11 ++
ios/styles/BoldStyle.mm | 11 ++
ios/styles/CodeBlockStyle.mm | 11 ++
ios/styles/HeadingStyleBase.mm | 11 ++
ios/styles/ImageStyle.mm | 11 ++
ios/styles/InlineCodeStyle.mm | 11 ++
ios/styles/ItalicStyle.mm | 11 ++
ios/styles/LinkStyle.mm | 12 ++
ios/styles/MentionStyle.mm | 11 ++
ios/styles/OrderedListStyle.mm | 11 ++
ios/styles/StrikethroughStyle.mm | 11 ++
ios/styles/UnderlineStyle.mm | 11 ++
ios/styles/UnorderedListStyle.mm | 11 ++
ios/utils/BaseStyleProtocol.h | 4 +
33 files changed, 518 insertions(+), 432 deletions(-)
create mode 100644 ios/inputParser/ConvertHtmlToPlainTextAndStylesResult.h
create mode 100644 ios/inputParser/ConvertHtmlToPlainTextAndStylesResult.mm
create mode 100644 ios/inputParser/HtmlHandler.h
rename ios/inputParser/{HtmlTokenizer.mm => HtmlHandler.mm} (85%)
delete mode 100644 ios/inputParser/HtmlTagInterpreter.h
delete mode 100644 ios/inputParser/HtmlTagInterpreter.mm
create mode 100644 ios/inputParser/HtmlTokenizationResult.h
create mode 100644 ios/inputParser/HtmlTokenizationResult.mm
delete mode 100644 ios/inputParser/HtmlTokenizer.h
delete mode 100644 ios/inputParser/StyleSanitizer.h
delete mode 100644 ios/inputParser/StyleSanitizer.mm
create mode 100644 ios/inputParser/TagHandlersFactory.h
create mode 100644 ios/inputParser/TagHandlersFactory.mm
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 9e6121c42..793905609 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -70,78 +70,6 @@ const DEBUG_SCROLLABLE = false;
// See: https://github.com/software-mansion/react-native-enriched/issues/229
const ANDROID_EXPERIMENTAL_SYNCHRONOUS_EVENTS = false;
-const generateHugeHtml = (repeat = 200) => {
- const parts: string[] = [];
- parts.push('');
-
- // small helper to make deterministic colors
- const colorAt = (i: number) => {
- const r = (37 * (i + 1)) % 256;
- const g = (83 * (i + 7)) % 256;
- const b = (199 * (i + 13)) % 256;
- const toHex = (n: number) => n.toString(16).padStart(2, '0');
- return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
- };
-
- for (let i = 0; i < repeat; i++) {
- const col = colorAt(i);
- const imgW = 200 + (i % 5) * 40;
- const imgH = 100 + (i % 3) * 30;
-
- parts.push(
- // Headings
- `\nSection ${i + 1} `,
- `\nSubsection ${i + 1}.1 `,
- `\nTopic ${i + 1}.1.a `,
-
- // Paragraph with mixed inline styles
- `\nThis is a bold and italic paragraph with underline , ` +
- `strike , inline_code_${i}, ` +
- `a link ${i} , ` +
- ` , ` +
- ` , ` +
- `colored text ${col} and some plain text to bulk it up.
`,
-
- // Line break
- `\n `,
-
- // Unordered list
- ``,
- `bullet A ${i} `,
- `bullet B ${i} `,
- `bullet C ${i} `,
- ` `,
-
- // Ordered list
- `\n`,
- `\nstep 1.${i} `,
- `\nstep 2.${i} `,
- `\nstep 3.${i} `,
- `\n `,
-
- // Blockquote
- `\n`,
- `\n“Blockquote line 1 for ${i}.”
`,
- `\n“Blockquote line 2 for ${i}.”
`,
- `\n `,
-
- // Code block (escaped characters)
- `\n`,
- `\nfor (let k = 0; k < ${i % 7}; k++) { console.log("block_${i}"); }
`,
- `\n `,
-
- // Image (self-closing)
- `\n
`
- );
- }
-
- parts.push('\n');
- return parts.join('');
-};
-
-const initialHugeHtml = generateHugeHtml();
-console.log(initialHugeHtml.length);
-
export default function App() {
const [isChannelPopupOpen, setIsChannelPopupOpen] = useState(false);
const [isUserPopupOpen, setIsUserPopupOpen] = useState(false);
diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm
index 0eb589d08..4fab52799 100644
--- a/ios/EnrichedTextInputView.mm
+++ b/ios/EnrichedTextInputView.mm
@@ -37,6 +37,7 @@ @implementation EnrichedTextInputView {
UILabel *_placeholderLabel;
UIColor *_placeholderColor;
BOOL _emitFocusBlur;
+ BOOL _didRunInitialMount;
}
// MARK: - Component utils
@@ -1456,8 +1457,13 @@ - (void)_performRelayout {
- (void)didMoveToWindow {
[super didMoveToWindow];
- [self layoutIfNeeded];
- [self anyTextMayHaveBeenModified];
+
+ if (self.window && !_didRunInitialMount) {
+ _didRunInitialMount = YES;
+
+ [self layoutIfNeeded];
+ [self anyTextMayHaveBeenModified];
+ }
}
// MARK: - UITextView delegate methods
diff --git a/ios/inputParser/AttributedStringBuilder.h b/ios/inputParser/AttributedStringBuilder.h
index e4b39657e..0d9a733df 100644
--- a/ios/inputParser/AttributedStringBuilder.h
+++ b/ios/inputParser/AttributedStringBuilder.h
@@ -7,6 +7,8 @@
- (void)apply:(NSArray *)processedStyles
toAttributedString:(NSMutableAttributedString *)attributedString
- offsetFromBeginning:(NSInteger)offset;
+ offsetFromBeginning:(NSInteger)offset
+ conflictingStyles:
+ (NSDictionary *> *)conflictingStyles;
@end
diff --git a/ios/inputParser/AttributedStringBuilder.mm b/ios/inputParser/AttributedStringBuilder.mm
index 632f5a227..96a2532d7 100644
--- a/ios/inputParser/AttributedStringBuilder.mm
+++ b/ios/inputParser/AttributedStringBuilder.mm
@@ -6,49 +6,46 @@ @implementation AttributedStringBuilder
- (void)apply:(NSArray *)processedStyles
toAttributedString:(NSMutableAttributedString *)attributedString
- offsetFromBeginning:(NSInteger)offset {
- NSArray *sorted = [processedStyles
- sortedArrayUsingComparator:^NSComparisonResult(NSArray *a, NSArray *b) {
- StylePair *pa = a[1];
- StylePair *pb = b[1];
-
- NSInteger la = offset + pa.rangeValue.rangeValue.location;
- NSInteger lb = offset + pb.rangeValue.rangeValue.location;
- return (la < lb ? NSOrderedDescending
- : (la > lb ? NSOrderedAscending : NSOrderedSame));
- }];
-
+ offsetFromBeginning:(NSInteger)offset
+ conflictingStyles:
+ (NSDictionary *> *)conflictingStyles {
[attributedString beginEditing];
- for (NSArray *arr in sorted) {
- NSNumber *type = arr[0];
- StylePair *pair = arr[1];
+ for (NSArray *processedStylePair in processedStyles) {
+ NSNumber *type = processedStylePair[0];
+ StylePair *pair = processedStylePair[1];
- NSRange r = NSMakeRange(offset + pair.rangeValue.rangeValue.location,
- pair.rangeValue.rangeValue.length);
+ NSRange pairRange = [pair.rangeValue rangeValue];
+ NSRange range = NSMakeRange(offset + pairRange.location, pairRange.length);
+ if (![self canApplyStyle:type
+ range:range
+ attributedString:attributedString
+ conflictingMap:conflictingStyles
+ stylesDict:self.stylesDict]) {
+ continue;
+ }
id style = self.stylesDict[type];
if ([type isEqualToNumber:@([LinkStyle getStyleType])]) {
- NSString *text = [attributedString.string substringWithRange:r];
+ NSString *text = [attributedString.string substringWithRange:range];
NSString *url = pair.styleValue;
BOOL isManual = [text isEqualToString:url];
[(LinkStyle *)style addLinkInAttributedString:attributedString
- range:r
+ range:range
text:text
url:url
manual:isManual];
} else if ([type isEqualToNumber:@([MentionStyle getStyleType])]) {
[(MentionStyle *)style addMentionInAttributedString:attributedString
- range:r
+ range:range
params:pair.styleValue];
} else if ([type isEqualToNumber:@([ImageStyle getStyleType])]) {
[(ImageStyle *)style addImageInAttributedString:attributedString
- range:r
+ range:range
imageData:pair.styleValue];
-
} else {
[style addAttributesInAttributedString:attributedString range:r];
}
@@ -57,4 +54,29 @@ - (void)apply:(NSArray *)processedStyles
[attributedString endEditing];
}
+- (BOOL)canApplyStyle:(NSNumber *)type
+ range:(NSRange)range
+ attributedString:(NSAttributedString *)string
+ conflictingMap:(NSDictionary *> *)confMap
+ stylesDict:
+ (NSDictionary> *)stylesDict {
+ NSArray *conflicts = confMap[type];
+ if (!conflicts || conflicts.count == 0)
+ return YES;
+
+ for (NSNumber *conflictType in conflicts) {
+ id conflictStyle = stylesDict[conflictType];
+ if (!conflictStyle)
+ continue;
+
+ NSArray *occurrences =
+ [conflictStyle findAllOccurencesInAttributedString:string range:range];
+
+ if (occurrences.count > 0) {
+ return NO;
+ }
+ }
+
+ return YES;
+}
@end
diff --git a/ios/inputParser/ConvertHtmlToPlainTextAndStylesResult.h b/ios/inputParser/ConvertHtmlToPlainTextAndStylesResult.h
new file mode 100644
index 000000000..ea805215a
--- /dev/null
+++ b/ios/inputParser/ConvertHtmlToPlainTextAndStylesResult.h
@@ -0,0 +1,7 @@
+#import
+
+@interface ConvertHtmlToPlainTextAndStylesResult : NSObject
+@property(nonatomic, strong) NSString *text;
+@property(nonatomic, strong) NSArray *styles;
+- (instancetype)initWithData:(NSString *)text styles:(NSArray *)styles;
+@end
diff --git a/ios/inputParser/ConvertHtmlToPlainTextAndStylesResult.mm b/ios/inputParser/ConvertHtmlToPlainTextAndStylesResult.mm
new file mode 100644
index 000000000..08ee8cdd2
--- /dev/null
+++ b/ios/inputParser/ConvertHtmlToPlainTextAndStylesResult.mm
@@ -0,0 +1,10 @@
+#import "ConvertHtmlToPlainTextAndStylesResult.h"
+
+@implementation ConvertHtmlToPlainTextAndStylesResult
+- (instancetype)initWithData:(NSString *)text styles:(NSArray *)styles {
+ self = [super init];
+ _text = text;
+ _styles = styles;
+ return self;
+}
+@end
diff --git a/ios/inputParser/HtmlHandler.h b/ios/inputParser/HtmlHandler.h
new file mode 100644
index 000000000..35908d3c0
--- /dev/null
+++ b/ios/inputParser/HtmlHandler.h
@@ -0,0 +1,10 @@
+#import
+
+@class ConvertHtmlToPlainTextAndStylesResult;
+@class HtmlTokenizationResult;
+
+@interface HtmlHandler : NSObject
+- (NSString *)initiallyProcessHtml:(NSString *)html;
+- (ConvertHtmlToPlainTextAndStylesResult *)getTextAndStylesFromHtml:
+ (NSString *)fixedHtml;
+@end
diff --git a/ios/inputParser/HtmlTokenizer.mm b/ios/inputParser/HtmlHandler.mm
similarity index 85%
rename from ios/inputParser/HtmlTokenizer.mm
rename to ios/inputParser/HtmlHandler.mm
index 8b45141f6..74846e904 100644
--- a/ios/inputParser/HtmlTokenizer.mm
+++ b/ios/inputParser/HtmlHandler.mm
@@ -1,13 +1,49 @@
-#import "HtmlTokenizer.h"
+#import "HtmlHandler.h"
+#import "ConvertHtmlToPlainTextAndStylesResult.h"
+#import "HtmlTokenizationResult.h"
#import "StringExtension.h"
#import "StyleHeaders.h"
+#import "TagHandlersFactory.h"
-@implementation HtmlTokenizationResult
-@end
+static const int MIN_HTML_SIZE = 13;
-@implementation HtmlTokenizer
+@implementation HtmlHandler
-static const int MIN_HTML_SIZE = 13;
+static NSDictionary *TagHandlers;
+
++ (void)initialize {
+ if (self != [HtmlHandler class])
+ return;
+ TagHandlers = MakeTagHandlers();
+}
+
+- (NSMutableArray *)convertTagsToStyles:(NSArray *)initiallyProcessedTags {
+ NSMutableArray *processedStyles = [NSMutableArray array];
+
+ for (NSArray *arr in initiallyProcessedTags) {
+ NSString *tagName = arr[0];
+ NSValue *tagRangeValue = arr[1];
+ NSString *params = arr.count > 2 ? arr[2] : @"";
+
+ TagHandler tagHandler = TagHandlers[tagName];
+ if (!tagHandler)
+ continue;
+
+ StylePair *pair = [StylePair new];
+ pair.rangeValue = tagRangeValue;
+
+ NSMutableArray *styleArr = [NSMutableArray array];
+ tagHandler(params, pair, styleArr);
+
+ if (styleArr.count == 0)
+ continue;
+
+ [styleArr addObject:pair];
+ [processedStyles addObject:styleArr];
+ }
+
+ return processedStyles;
+}
- (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html {
NSString *fixedHtml = nullptr;
@@ -296,11 +332,8 @@ - (HtmlTokenizationResult *)tokenize:(NSString *)fixedHtml {
}
}
- HtmlTokenizationResult *result = [[HtmlTokenizationResult alloc] init];
- result.plainText = plainText;
- result.initialTags = initiallyProcessedTags;
-
- return result;
+ return [[HtmlTokenizationResult alloc] initWithData:plainText
+ tags:initiallyProcessedTags];
}
- (void)finalizeTag:(NSMutableString *)tagName
@@ -323,4 +356,31 @@ - (void)finalizeTag:(NSMutableString *)tagName
[ongoingTags removeObjectForKey:tagName];
}
+- (ConvertHtmlToPlainTextAndStylesResult *)getTextAndStylesFromHtml:
+ (NSString *)fixedHtml {
+ HtmlTokenizationResult *tagTokens = [self tokenize:fixedHtml];
+ NSMutableArray *processed = [self convertTagsToStyles:tagTokens.tags];
+
+ NSArray *sorted = [processed sortedArrayUsingComparator:^NSComparisonResult(
+ NSArray *firstArray, NSArray *secondArray) {
+ StylePair *firstStylePair = firstArray[1];
+ StylePair *secondStylePair = secondArray[1];
+
+ NSRange firstStyleRange = [firstStylePair.rangeValue rangeValue];
+ NSRange secondStyleRange = [secondStylePair.rangeValue rangeValue];
+ NSInteger firstStyleLocation = firstStyleRange.location;
+ NSInteger secondStyleLocation = secondStyleRange.location;
+
+ if (firstStyleLocation < secondStyleLocation)
+ return NSOrderedDescending;
+ if (firstStyleLocation > secondStyleLocation)
+ return NSOrderedAscending;
+ return NSOrderedSame;
+ }];
+
+ return
+ [[ConvertHtmlToPlainTextAndStylesResult alloc] initWithData:tagTokens.text
+ styles:sorted];
+}
+
@end
diff --git a/ios/inputParser/HtmlTagInterpreter.h b/ios/inputParser/HtmlTagInterpreter.h
deleted file mode 100644
index 3fdb825be..000000000
--- a/ios/inputParser/HtmlTagInterpreter.h
+++ /dev/null
@@ -1,10 +0,0 @@
-#import
-
-@class StylePair;
-
-@interface HtmlTagInterpreter : NSObject
-
-- (NSMutableArray *)convertTags:(NSArray *)initialTags
- plainText:(NSString *)plainText;
-
-@end
diff --git a/ios/inputParser/HtmlTagInterpreter.mm b/ios/inputParser/HtmlTagInterpreter.mm
deleted file mode 100644
index 16d19b9ce..000000000
--- a/ios/inputParser/HtmlTagInterpreter.mm
+++ /dev/null
@@ -1,181 +0,0 @@
-#import "HtmlTagInterpreter.h"
-#import "ImageData.h"
-#import "MentionParams.h"
-#import "StyleHeaders.h"
-#import "StylePair.h"
-
-@implementation HtmlTagInterpreter
-
-- (NSMutableArray *)convertTags:(NSArray *)initiallyProcessedTags
- plainText:(NSString *)plainText {
- // process tags into proper StyleType + StylePair values
- NSMutableArray *processedStyles = [[NSMutableArray alloc] init];
-
- for (NSArray *arr in initiallyProcessedTags) {
- NSString *tagName = (NSString *)arr[0];
- NSValue *tagRangeValue = (NSValue *)arr[1];
- NSMutableString *params = [[NSMutableString alloc] initWithString:@""];
- if (arr.count > 2) {
- [params appendString:(NSString *)arr[2]];
- }
-
- NSMutableArray *styleArr = [[NSMutableArray alloc] init];
- StylePair *stylePair = [[StylePair alloc] init];
- if ([tagName isEqualToString:@"b"]) {
- [styleArr addObject:@([BoldStyle getStyleType])];
- } else if ([tagName isEqualToString:@"i"]) {
- [styleArr addObject:@([ItalicStyle getStyleType])];
- } else if ([tagName isEqualToString:@"img"]) {
- NSRegularExpression *srcRegex =
- [NSRegularExpression regularExpressionWithPattern:@"src=\"([^\"]+)\""
- options:0
- error:nullptr];
- NSTextCheckingResult *match =
- [srcRegex firstMatchInString:params
- options:0
- range:NSMakeRange(0, params.length)];
-
- if (match == nullptr) {
- continue;
- }
-
- NSRange srcRange = match.range;
- [styleArr addObject:@([ImageStyle getStyleType])];
- // cut only the uri from the src="..." string
- NSString *uri =
- [params substringWithRange:NSMakeRange(srcRange.location + 5,
- srcRange.length - 6)];
- ImageData *imageData = [[ImageData alloc] init];
- imageData.uri = uri;
-
- NSRegularExpression *widthRegex = [NSRegularExpression
- regularExpressionWithPattern:@"width=\"([0-9.]+)\""
- options:0
- error:nil];
- NSTextCheckingResult *widthMatch =
- [widthRegex firstMatchInString:params
- options:0
- range:NSMakeRange(0, params.length)];
-
- if (widthMatch) {
- NSString *widthString =
- [params substringWithRange:[widthMatch rangeAtIndex:1]];
- imageData.width = [widthString floatValue];
- }
-
- NSRegularExpression *heightRegex = [NSRegularExpression
- regularExpressionWithPattern:@"height=\"([0-9.]+)\""
- options:0
- error:nil];
- NSTextCheckingResult *heightMatch =
- [heightRegex firstMatchInString:params
- options:0
- range:NSMakeRange(0, params.length)];
-
- if (heightMatch) {
- NSString *heightString =
- [params substringWithRange:[heightMatch rangeAtIndex:1]];
- imageData.height = [heightString floatValue];
- }
-
- stylePair.styleValue = imageData;
- } else if ([tagName isEqualToString:@"u"]) {
- [styleArr addObject:@([UnderlineStyle getStyleType])];
- } else if ([tagName isEqualToString:@"s"]) {
- [styleArr addObject:@([StrikethroughStyle getStyleType])];
- } else if ([tagName isEqualToString:@"code"]) {
- [styleArr addObject:@([InlineCodeStyle getStyleType])];
- } else if ([tagName isEqualToString:@"a"]) {
- NSRegularExpression *hrefRegex =
- [NSRegularExpression regularExpressionWithPattern:@"href=\".+\""
- options:0
- error:nullptr];
- NSTextCheckingResult *match =
- [hrefRegex firstMatchInString:params
- options:0
- range:NSMakeRange(0, params.length)];
-
- if (match == nullptr) {
- // same as on Android, no href (or empty href) equals no link style
- continue;
- }
-
- NSRange hrefRange = match.range;
- [styleArr addObject:@([LinkStyle getStyleType])];
- // cut only the url from the href="..." string
- NSString *url =
- [params substringWithRange:NSMakeRange(hrefRange.location + 6,
- hrefRange.length - 7)];
- stylePair.styleValue = url;
- } else if ([tagName isEqualToString:@"mention"]) {
- [styleArr addObject:@([MentionStyle getStyleType])];
- // extract html expression into dict using some regex
- NSMutableDictionary *paramsDict = [[NSMutableDictionary alloc] init];
- NSString *pattern = @"(\\w+)=\"([^\"]*)\"";
- NSRegularExpression *regex =
- [NSRegularExpression regularExpressionWithPattern:pattern
- options:0
- error:nil];
-
- [regex enumerateMatchesInString:params
- options:0
- range:NSMakeRange(0, params.length)
- usingBlock:^(NSTextCheckingResult *_Nullable result,
- NSMatchingFlags flags,
- BOOL *_Nonnull stop) {
- if (result.numberOfRanges == 3) {
- NSString *key = [params
- substringWithRange:[result rangeAtIndex:1]];
- NSString *value = [params
- substringWithRange:[result rangeAtIndex:2]];
- paramsDict[key] = value;
- }
- }];
-
- MentionParams *mentionParams = [[MentionParams alloc] init];
- mentionParams.text = paramsDict[@"text"];
- mentionParams.indicator = paramsDict[@"indicator"];
-
- [paramsDict removeObjectsForKeys:@[ @"text", @"indicator" ]];
- NSError *error;
- NSData *attrsData = [NSJSONSerialization dataWithJSONObject:paramsDict
- options:0
- error:&error];
- NSString *formattedAttrsString =
- [[NSString alloc] initWithData:attrsData
- encoding:NSUTF8StringEncoding];
- mentionParams.attributes = formattedAttrsString;
-
- stylePair.styleValue = mentionParams;
- } else if ([[tagName substringWithRange:NSMakeRange(0, 1)]
- isEqualToString:@"h"]) {
- if ([tagName isEqualToString:@"h1"]) {
- [styleArr addObject:@([H1Style getStyleType])];
- } else if ([tagName isEqualToString:@"h2"]) {
- [styleArr addObject:@([H2Style getStyleType])];
- } else if ([tagName isEqualToString:@"h3"]) {
- [styleArr addObject:@([H3Style getStyleType])];
- }
- } else if ([tagName isEqualToString:@"ul"]) {
- [styleArr addObject:@([UnorderedListStyle getStyleType])];
- } else if ([tagName isEqualToString:@"ol"]) {
- [styleArr addObject:@([OrderedListStyle getStyleType])];
- } else if ([tagName isEqualToString:@"blockquote"]) {
- [styleArr addObject:@([BlockQuoteStyle getStyleType])];
- } else if ([tagName isEqualToString:@"codeblock"]) {
- [styleArr addObject:@([CodeBlockStyle getStyleType])];
- } else {
- // some other external tags like span just don't get put into the
- // processed styles
- continue;
- }
-
- stylePair.rangeValue = tagRangeValue;
- [styleArr addObject:stylePair];
- [processedStyles addObject:styleArr];
- }
-
- return processedStyles;
-}
-
-@end
diff --git a/ios/inputParser/HtmlTokenizationResult.h b/ios/inputParser/HtmlTokenizationResult.h
new file mode 100644
index 000000000..1546c363a
--- /dev/null
+++ b/ios/inputParser/HtmlTokenizationResult.h
@@ -0,0 +1,8 @@
+#import
+
+@interface HtmlTokenizationResult : NSObject
+@property(nonatomic, strong) NSString *text;
+@property(nonatomic, strong) NSMutableArray *tags;
+- (instancetype)initWithData:(NSString *_Nonnull)text
+ tags:(NSMutableArray *_Nonnull)tags;
+@end
diff --git a/ios/inputParser/HtmlTokenizationResult.mm b/ios/inputParser/HtmlTokenizationResult.mm
new file mode 100644
index 000000000..9de04d43c
--- /dev/null
+++ b/ios/inputParser/HtmlTokenizationResult.mm
@@ -0,0 +1,11 @@
+#import "HtmlTokenizationResult.h"
+
+@implementation HtmlTokenizationResult
+- (instancetype)initWithData:(NSString *)text
+ tags:(NSMutableArray *_Nonnull)tags {
+ self = [super init];
+ _text = text;
+ _tags = tags;
+ return self;
+}
+@end
diff --git a/ios/inputParser/HtmlTokenizer.h b/ios/inputParser/HtmlTokenizer.h
deleted file mode 100644
index a25ea26b2..000000000
--- a/ios/inputParser/HtmlTokenizer.h
+++ /dev/null
@@ -1,16 +0,0 @@
-#import
-
-@interface HtmlTokenizationResult : NSObject
-@property(nonatomic, strong) NSString *plainText;
-@property(nonatomic, strong) NSMutableArray *initialTags;
-@end
-
-@interface HtmlTokenizer : NSObject
-- (NSString *)initiallyProcessHtml:(NSString *)html;
-- (NSString *)stringByAddingNewlinesToTag:(NSString *)tag
- inString:(NSString *)html
- leading:(BOOL)leading
- trailing:(BOOL)trailing;
-
-- (HtmlTokenizationResult *)tokenize:(NSString *)fixedHtml;
-@end
diff --git a/ios/inputParser/InputParser.h b/ios/inputParser/InputParser.h
index 5b922abc9..7d04ea9c5 100644
--- a/ios/inputParser/InputParser.h
+++ b/ios/inputParser/InputParser.h
@@ -1,6 +1,8 @@
#pragma once
#import
+@class ConvertHtmlToPlainTextAndStylesResult;
+
@interface InputParser : NSObject
- (instancetype _Nonnull)initWithInput:(id _Nonnull)input;
- (NSString *_Nonnull)parseToHtmlFromRange:(NSRange)range;
diff --git a/ios/inputParser/InputParser.mm b/ios/inputParser/InputParser.mm
index b73e78636..4497931ac 100644
--- a/ios/inputParser/InputParser.mm
+++ b/ios/inputParser/InputParser.mm
@@ -2,30 +2,23 @@
#import "EnrichedTextInputView.h"
#import "AttributedStringBuilder.h"
+#import "ConvertHtmlToPlainTextAndStylesResult.h"
#import "HtmlBuilder.h"
-#import "HtmlTagInterpreter.h"
-#import "HtmlTokenizer.h"
-#import "StyleSanitizer.h"
+#import "HtmlHandler.h"
#import "StyleHeaders.h"
@implementation InputParser {
EnrichedTextInputView *_input;
- HtmlTokenizer *_tokenizer;
- HtmlTagInterpreter *_interpreter;
- StyleSanitizer *_sanitizer;
AttributedStringBuilder *_builder;
HtmlBuilder *_htmlBuilder;
+ HtmlHandler *_htmlHandler;
}
- (instancetype)initWithInput:(id)input {
self = [super init];
_input = (EnrichedTextInputView *)input;
- _tokenizer = [HtmlTokenizer new];
- _interpreter = [HtmlTagInterpreter new];
- _sanitizer = [StyleSanitizer new];
-
_builder = [AttributedStringBuilder new];
_builder.stylesDict = _input->stylesDict;
@@ -33,40 +26,25 @@ - (instancetype)initWithInput:(id)input {
_htmlBuilder.stylesDict = _input->stylesDict;
_htmlBuilder.input = _input;
- return self;
-}
-
-- (NSArray *)getTextAndStylesFromHtml:(NSString *)html {
- if (!html)
- return @[ @"", @[] ];
- HtmlTokenizationResult *tokens = [_tokenizer tokenize:html];
+ _htmlHandler = [HtmlHandler new];
- NSMutableArray *processed = [_interpreter convertTags:tokens.initialTags
- plainText:tokens.plainText];
-
- [_sanitizer sanitizeStyles:processed
- blocking:_input.blockingStyles
- conflicting:_input.conflictingStyles];
-
- return @[ tokens.plainText, processed ];
+ return self;
}
- (void)replaceWholeFromHtml:(NSString *)html
notifyAnyTextMayHaveBeenModified:(BOOL)notifyAnyTextMayHaveBeenModified {
- NSArray *parsed = [self getTextAndStylesFromHtml:html];
- NSString *plain = parsed[0];
- NSArray *styles = parsed[1];
-
- NSLog(@"replace whole from html %@", html);
+ ConvertHtmlToPlainTextAndStylesResult *plainTextAndStyles =
+ [_htmlHandler getTextAndStylesFromHtml:html];
NSMutableAttributedString *attributedString =
[[NSMutableAttributedString alloc]
- initWithString:plain
+ initWithString:plainTextAndStyles.text
attributes:_input->defaultTypingAttributes];
- [_builder apply:styles
+ [_builder apply:plainTextAndStyles.styles
toAttributedString:attributedString
- offsetFromBeginning:0];
+ offsetFromBeginning:0
+ conflictingStyles:_input->conflictingStyles];
NSTextStorage *storage = _input->textView.textStorage;
[storage setAttributedString:attributedString];
@@ -78,15 +56,17 @@ - (void)replaceWholeFromHtml:(NSString *)html
}
- (void)replaceFromHtml:(NSString *)html range:(NSRange)range {
- NSArray *parsed = [self getTextAndStylesFromHtml:html];
- NSString *plainText = parsed[0];
- NSArray *styles = parsed[1];
+ ConvertHtmlToPlainTextAndStylesResult *plainTextAndStyles =
+ [_htmlHandler getTextAndStylesFromHtml:html];
NSMutableAttributedString *inserted = [[NSMutableAttributedString alloc]
- initWithString:plainText
+ initWithString:plainTextAndStyles.text
attributes:_input->defaultTypingAttributes];
- [_builder apply:styles toAttributedString:inserted offsetFromBeginning:0];
+ [_builder apply:plainTextAndStyles.styles
+ toAttributedString:inserted
+ offsetFromBeginning:0
+ conflictingStyles:_input->conflictingStyles];
NSTextStorage *storage = _input->textView.textStorage;
@@ -108,15 +88,17 @@ - (void)replaceFromHtml:(NSString *)html range:(NSRange)range {
- (void)insertFromHtml:(NSString *)html location:(NSInteger)location {
- NSArray *parsed = [self getTextAndStylesFromHtml:html];
- NSString *plain = parsed[0];
- NSArray *styles = parsed[1];
+ ConvertHtmlToPlainTextAndStylesResult *plainTextAndStyles =
+ [_htmlHandler getTextAndStylesFromHtml:html];
NSMutableAttributedString *inserted = [[NSMutableAttributedString alloc]
- initWithString:plain
+ initWithString:plainTextAndStyles.text
attributes:_input->defaultTypingAttributes];
- [_builder apply:styles toAttributedString:inserted offsetFromBeginning:0];
+ [_builder apply:plainTextAndStyles.styles
+ toAttributedString:inserted
+ offsetFromBeginning:0
+ conflictingStyles:_input->conflictingStyles];
[_input->textView.textStorage beginEditing];
[_input->textView.textStorage insertAttributedString:inserted
@@ -127,7 +109,7 @@ - (void)insertFromHtml:(NSString *)html location:(NSInteger)location {
}
- (NSString *)initiallyProcessHtml:(NSString *)html {
- return [_tokenizer initiallyProcessHtml:html];
+ return [_htmlHandler initiallyProcessHtml:html];
}
#pragma mark - NSAttributedString → HTML
diff --git a/ios/inputParser/StyleSanitizer.h b/ios/inputParser/StyleSanitizer.h
deleted file mode 100644
index 0625f1876..000000000
--- a/ios/inputParser/StyleSanitizer.h
+++ /dev/null
@@ -1,11 +0,0 @@
-#import
-
-@interface StyleSanitizer : NSObject
-
-- (void)sanitizeStyles:(NSMutableArray *)styles
- blocking:
- (NSDictionary *> *)blocking
- conflicting:
- (NSDictionary *> *)conflicting;
-
-@end
diff --git a/ios/inputParser/StyleSanitizer.mm b/ios/inputParser/StyleSanitizer.mm
deleted file mode 100644
index 86cbaab44..000000000
--- a/ios/inputParser/StyleSanitizer.mm
+++ /dev/null
@@ -1,63 +0,0 @@
-#import "StyleSanitizer.h"
-#import "StylePair.h"
-
-@implementation StyleSanitizer
-
-- (void)sanitizeStyles:(NSMutableArray *)styles
- blocking:
- (NSDictionary *> *)blocking
- conflicting:
- (NSDictionary *> *)conflicting {
- NSMutableDictionary *> *rangeToTypes =
- [NSMutableDictionary new];
-
- for (NSArray *entry in styles) {
- NSNumber *type = entry[0];
- NSValue *rKey = ((StylePair *)entry[1]).rangeValue;
-
- if (!rangeToTypes[rKey]) {
- rangeToTypes[rKey] = [NSMutableSet new];
- }
- [rangeToTypes[rKey] addObject:type];
- }
-
- for (NSValue *rKey in rangeToTypes) {
- NSMutableSet *types = rangeToTypes[rKey];
-
- for (NSNumber *type in [types copy]) {
-
- // BLOCKING: remove style if blocked
- for (NSNumber *b in blocking[type]) {
- if ([types containsObject:b]) {
- [types removeObject:type];
- break;
- }
- }
- if (![types containsObject:type])
- continue;
-
- for (NSNumber *c in conflicting[type]) {
- if ([types containsObject:c]) {
- [types removeObject:c];
- }
- }
- }
- }
-
- NSMutableArray *final = [NSMutableArray new];
-
- for (NSArray *entry in styles) {
- NSNumber *type = entry[0];
- StylePair *pair = entry[1];
-
- NSMutableSet *set = rangeToTypes[pair.rangeValue];
- if ([set containsObject:type]) {
- [final addObject:@[ type, pair ]];
- }
- }
-
- [styles removeAllObjects];
- [styles addObjectsFromArray:final];
-}
-
-@end
diff --git a/ios/inputParser/TagHandlersFactory.h b/ios/inputParser/TagHandlersFactory.h
new file mode 100644
index 000000000..c6475e8fd
--- /dev/null
+++ b/ios/inputParser/TagHandlersFactory.h
@@ -0,0 +1,8 @@
+#import
+
+@class StylePair;
+
+typedef void (^TagHandler)(NSString *params, StylePair *pair,
+ NSMutableArray *styleArr);
+
+FOUNDATION_EXPORT NSDictionary *MakeTagHandlers(void);
diff --git a/ios/inputParser/TagHandlersFactory.mm b/ios/inputParser/TagHandlersFactory.mm
new file mode 100644
index 000000000..ab212a17f
--- /dev/null
+++ b/ios/inputParser/TagHandlersFactory.mm
@@ -0,0 +1,163 @@
+#import "TagHandlersFactory.h"
+
+#import "ImageData.h"
+#import "MentionParams.h"
+#import "StyleHeaders.h"
+#import "StylePair.h"
+
+NSDictionary *MakeTagHandlers(void) {
+ static NSDictionary *taghandlers;
+ static dispatch_once_t onceToken;
+
+ dispatch_once(&onceToken, ^{
+ taghandlers =
+ @{@"b" : ^(NSString *params, StylePair *pair, NSMutableArray *styleArr){
+ [styleArr addObject:@([BoldStyle getStyleType])];
+ },
+ @"strong": ^(NSString *params, StylePair *pair, NSMutableArray *styleArr) { // alias
+ [styleArr addObject:@([BoldStyle getStyleType])];
+ },
+ @"i": ^(NSString *params, StylePair *pair, NSMutableArray *styleArr) {
+ [styleArr addObject:@([ItalicStyle getStyleType])];
+ },
+
+ @"em": ^(NSString *params, StylePair *pair, NSMutableArray *styleArr) { // alias
+ [styleArr addObject:@([ItalicStyle getStyleType])];
+ },
+
+ @"u": ^(NSString *params, StylePair *pair, NSMutableArray *styleArr) {
+ [styleArr addObject:@([UnderlineStyle getStyleType])];
+ },
+
+ @"s": ^(NSString *params, StylePair *pair, NSMutableArray *styleArr) {
+ [styleArr addObject:@([StrikethroughStyle getStyleType])];
+ },
+
+ @"code": ^(NSString *params, StylePair *pair, NSMutableArray *styleArr) {
+ [styleArr addObject:@([InlineCodeStyle getStyleType])];
+ },
+ @"img": ^(NSString *params, StylePair *pair, NSMutableArray *styleArr) {
+ ImageData *img = [ImageData new];
+
+ NSRegularExpression *srcRegex =
+ [NSRegularExpression regularExpressionWithPattern:@"src=\"([^\"]+)\""
+ options:0
+ error:nil];
+ NSTextCheckingResult *sercMatch =
+ [srcRegex firstMatchInString:params
+ options:0
+ range:NSMakeRange(0, params.length)];
+ if (!sercMatch)
+ return;
+
+ img.uri = [params substringWithRange:[sercMatch rangeAtIndex:1]];
+
+ NSRegularExpression *widthRegex =
+ [NSRegularExpression regularExpressionWithPattern:@"width=\"([0-9.]+)\""
+ options:0
+ error:nil];
+ NSTextCheckingResult *widthMatch =
+ [widthRegex firstMatchInString:params
+ options:0
+ range:NSMakeRange(0, params.length)];
+ if (widthMatch) {
+ img.width =
+ [[params substringWithRange:[widthMatch rangeAtIndex:1]] floatValue];
+ }
+
+ NSRegularExpression *heightRegex = [NSRegularExpression
+ regularExpressionWithPattern:@"height=\"([0-9.]+)\""
+ options:0
+ error:nil];
+ NSTextCheckingResult *heightMatch =
+ [heightRegex firstMatchInString:params
+ options:0
+ range:NSMakeRange(0, params.length)];
+ if (heightMatch) {
+ img.height =
+ [[params substringWithRange:[heightMatch rangeAtIndex:1]] floatValue];
+ }
+
+ pair.styleValue = img;
+ [styleArr addObject:@([ImageStyle getStyleType])];
+ },
+ @"a": ^(NSString *params, StylePair *pair, NSMutableArray *styleArr) {
+ NSRegularExpression *hrefRegex =
+ [NSRegularExpression regularExpressionWithPattern:@"href=\"([^\"]*)\""
+ options:0
+ error:nil];
+
+ NSTextCheckingResult *match =
+ [hrefRegex firstMatchInString:params
+ options:0
+ range:NSMakeRange(0, params.length)];
+
+ if (!match)
+ return;
+ NSString *url = [params substringWithRange:[match rangeAtIndex:1]];
+
+ pair.styleValue = url;
+ [styleArr addObject:@([LinkStyle getStyleType])];
+ },
+ @"mention": ^(NSString *params, StylePair *pair, NSMutableArray *styleArr) {
+ NSMutableDictionary *dict = [NSMutableDictionary dictionary];
+
+ NSRegularExpression *re =
+ [NSRegularExpression regularExpressionWithPattern:@"(\\w+)=\"([^\"]*)\""
+ options:0
+ error:nil];
+
+ [re enumerateMatchesInString:params
+ options:0
+ range:NSMakeRange(0, params.length)
+ usingBlock:^(NSTextCheckingResult *res,
+ NSMatchingFlags flags, BOOL *stop) {
+ if (res.numberOfRanges == 3) {
+ NSString *k =
+ [params substringWithRange:[res rangeAtIndex:1]];
+ NSString *v =
+ [params substringWithRange:[res rangeAtIndex:2]];
+ dict[k] = v;
+ }
+ }];
+
+ MentionParams *mp = [MentionParams new];
+ mp.text = dict[@"text"];
+ mp.indicator = dict[@"indicator"];
+
+ [dict removeObjectsForKeys:@[ @"text", @"indicator" ]];
+ NSData *json = [NSJSONSerialization dataWithJSONObject:dict
+ options:0
+ error:nil];
+ mp.attributes = [[NSString alloc] initWithData:json
+ encoding:NSUTF8StringEncoding];
+
+ pair.styleValue = mp;
+ [styleArr addObject:@([MentionStyle getStyleType])];
+ },
+ @"h1": ^(NSString *params, StylePair *pair, NSMutableArray *styleArr) {
+ [styleArr addObject:@([H1Style getStyleType])];
+ },
+ @"h2": ^(NSString *params, StylePair *pair, NSMutableArray *styleArr) {
+ [styleArr addObject:@([H2Style getStyleType])];
+ },
+ @"h3": ^(NSString *params, StylePair *pair, NSMutableArray *styleArr) {
+ [styleArr addObject:@([H3Style getStyleType])];
+ },
+ @"ul": ^(NSString *params, StylePair *pair, NSMutableArray *styleArr) {
+ [styleArr addObject:@([UnorderedListStyle getStyleType])];
+ },
+ @"ol": ^(NSString *params, StylePair *pair, NSMutableArray *styleArr) {
+ [styleArr addObject:@([OrderedListStyle getStyleType])];
+ },
+ @"blockquote": ^(NSString *params, StylePair *pair, NSMutableArray *styleArr) {
+ [styleArr addObject:@([BlockQuoteStyle getStyleType])];
+ },
+ @"codeblock": ^(NSString *params, StylePair *pair, NSMutableArray *styleArr) {
+ [styleArr addObject:@([CodeBlockStyle getStyleType])];
+ },
+};
+});
+
+return taghandlers;
+}
diff --git a/ios/styles/BlockQuoteStyle.mm b/ios/styles/BlockQuoteStyle.mm
index 40d5cc13b..aa459ad32 100644
--- a/ios/styles/BlockQuoteStyle.mm
+++ b/ios/styles/BlockQuoteStyle.mm
@@ -291,6 +291,17 @@ - (BOOL)anyOccurence:(NSRange)range {
}];
}
+- (NSArray *_Nullable)
+ findAllOccurencesInAttributedString:(NSAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils all:NSParagraphStyleAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
+}
+
// general checkup correcting blockquote color
// since links, mentions and inline code affects coloring, the checkup gets done
// only outside of them
diff --git a/ios/styles/BoldStyle.mm b/ios/styles/BoldStyle.mm
index f52ccf7a1..7d70e3e54 100644
--- a/ios/styles/BoldStyle.mm
+++ b/ios/styles/BoldStyle.mm
@@ -172,4 +172,15 @@ - (BOOL)anyOccurence:(NSRange)range {
}];
}
+- (NSArray *_Nullable)
+ findAllOccurencesInAttributedString:(NSAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils all:NSFontAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
+}
+
@end
diff --git a/ios/styles/CodeBlockStyle.mm b/ios/styles/CodeBlockStyle.mm
index ebb7369a4..65c5ab9cc 100644
--- a/ios/styles/CodeBlockStyle.mm
+++ b/ios/styles/CodeBlockStyle.mm
@@ -281,6 +281,17 @@ - (BOOL)anyOccurence:(NSRange)range {
}];
}
+- (NSArray *_Nullable)
+ findAllOccurencesInAttributedString:(NSAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils all:NSParagraphStyleAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
+}
+
- (void)manageCodeBlockFontAndColor {
if ([[_input->config codeBlockFgColor]
isEqualToColor:[_input->config primaryColor]]) {
diff --git a/ios/styles/HeadingStyleBase.mm b/ios/styles/HeadingStyleBase.mm
index a5953b6ce..f1c6a524c 100644
--- a/ios/styles/HeadingStyleBase.mm
+++ b/ios/styles/HeadingStyleBase.mm
@@ -215,6 +215,17 @@ - (BOOL)anyOccurence:(NSRange)range {
}];
}
+- (NSArray *_Nullable)
+ findAllOccurencesInAttributedString:(NSAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils all:NSFontAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value: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
diff --git a/ios/styles/ImageStyle.mm b/ios/styles/ImageStyle.mm
index a2aa24466..287788a1d 100644
--- a/ios/styles/ImageStyle.mm
+++ b/ios/styles/ImageStyle.mm
@@ -113,6 +113,17 @@ - (BOOL)detectStyle:(NSRange)range {
}];
}
+- (NSArray *_Nullable)
+ findAllOccurencesInAttributedString:(NSAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils all:ImageAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
+}
+
- (ImageData *)getImageDataAt:(NSUInteger)location {
NSRange imageRange = NSMakeRange(0, 0);
NSRange inputRange = NSMakeRange(0, _input->textView.textStorage.length);
diff --git a/ios/styles/InlineCodeStyle.mm b/ios/styles/InlineCodeStyle.mm
index c7fc71c63..df980d1ed 100644
--- a/ios/styles/InlineCodeStyle.mm
+++ b/ios/styles/InlineCodeStyle.mm
@@ -239,4 +239,15 @@ - (BOOL)anyOccurence:(NSRange)range {
}];
}
+- (NSArray *_Nullable)
+ findAllOccurencesInAttributedString:(NSAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils all:NSBackgroundColorAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
+}
+
@end
diff --git a/ios/styles/ItalicStyle.mm b/ios/styles/ItalicStyle.mm
index f63e48c2c..feeb6fcb8 100644
--- a/ios/styles/ItalicStyle.mm
+++ b/ios/styles/ItalicStyle.mm
@@ -153,4 +153,15 @@ - (BOOL)anyOccurence:(NSRange)range {
}];
}
+- (NSArray *_Nullable)
+ findAllOccurencesInAttributedString:(NSAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils all:NSFontAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
+}
+
@end
diff --git a/ios/styles/LinkStyle.mm b/ios/styles/LinkStyle.mm
index 1d7891acc..8846a5ebe 100644
--- a/ios/styles/LinkStyle.mm
+++ b/ios/styles/LinkStyle.mm
@@ -197,6 +197,18 @@ - (BOOL)anyOccurence:(NSRange)range {
}];
}
+- (NSArray *_Nullable)
+ findAllOccurencesInAttributedString:(NSAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils
+ allMultiple:@[ ManualLinkAttributeName, AutomaticLinkAttributeName ]
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
+}
+
// MARK: - Public non-standard methods
- (void)addLink:(NSString *)text
diff --git a/ios/styles/MentionStyle.mm b/ios/styles/MentionStyle.mm
index 9b1c5d33f..4acaa326b 100644
--- a/ios/styles/MentionStyle.mm
+++ b/ios/styles/MentionStyle.mm
@@ -224,6 +224,17 @@ - (BOOL)anyOccurence:(NSRange)range {
}];
}
+- (NSArray *_Nullable)
+ findAllOccurencesInAttributedString:(NSAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils all:MentionAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
+}
+
// MARK: - Public non-standard methods
- (void)addMention:(NSString *)indicator
diff --git a/ios/styles/OrderedListStyle.mm b/ios/styles/OrderedListStyle.mm
index 690295c9e..76cc137b9 100644
--- a/ios/styles/OrderedListStyle.mm
+++ b/ios/styles/OrderedListStyle.mm
@@ -342,4 +342,15 @@ - (BOOL)anyOccurence:(NSRange)range {
}];
}
+- (NSArray *_Nullable)
+ findAllOccurencesInAttributedString:(NSAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils all:NSParagraphStyleAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
+}
+
@end
diff --git a/ios/styles/StrikethroughStyle.mm b/ios/styles/StrikethroughStyle.mm
index 8eaf26b14..516737049 100644
--- a/ios/styles/StrikethroughStyle.mm
+++ b/ios/styles/StrikethroughStyle.mm
@@ -118,4 +118,15 @@ - (BOOL)anyOccurence:(NSRange)range {
}];
}
+- (NSArray *_Nullable)
+ findAllOccurencesInAttributedString:(NSAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils all:NSStrikethroughStyleAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
+}
+
@end
diff --git a/ios/styles/UnderlineStyle.mm b/ios/styles/UnderlineStyle.mm
index ff2960d3a..ffe085347 100644
--- a/ios/styles/UnderlineStyle.mm
+++ b/ios/styles/UnderlineStyle.mm
@@ -155,4 +155,15 @@ - (BOOL)anyOccurence:(NSRange)range {
}];
}
+- (NSArray *_Nullable)
+ findAllOccurencesInAttributedString:(NSAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils all:NSUnderlineStyleAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
+}
+
@end
diff --git a/ios/styles/UnorderedListStyle.mm b/ios/styles/UnorderedListStyle.mm
index d3672a50e..3dd744ee6 100644
--- a/ios/styles/UnorderedListStyle.mm
+++ b/ios/styles/UnorderedListStyle.mm
@@ -338,4 +338,15 @@ - (BOOL)anyOccurence:(NSRange)range {
}];
}
+- (NSArray *_Nullable)
+ findAllOccurencesInAttributedString:(NSAttributedString *)attributedString
+ range:(NSRange)range {
+ return [OccurenceUtils all:NSParagraphStyleAttributeName
+ inString:attributedString
+ inRange:range
+ withCondition:^BOOL(id _Nullable value, NSRange range) {
+ return [self styleCondition:value:range];
+ }];
+}
+
@end
diff --git a/ios/utils/BaseStyleProtocol.h b/ios/utils/BaseStyleProtocol.h
index a32a0ae63..d7a4b6eb7 100644
--- a/ios/utils/BaseStyleProtocol.h
+++ b/ios/utils/BaseStyleProtocol.h
@@ -23,4 +23,8 @@
- (BOOL)detectStyle:(NSRange)range;
- (BOOL)anyOccurence:(NSRange)range;
- (NSArray *_Nullable)findAllOccurences:(NSRange)range;
+- (NSArray *_Nullable)
+ findAllOccurencesInAttributedString:
+ (NSAttributedString *_Nonnull)attributedString
+ range:(NSRange)range;
@end
From 0305136d25ed711df9c048768be446c23a27bb2f Mon Sep 17 00:00:00 2001
From: IvanIhnatsiuk
Date: Thu, 11 Dec 2025 21:55:44 +0100
Subject: [PATCH 5/8] fix: empty defaultValue applying
---
ios/EnrichedTextInputView.mm | 20 +++++++++---------
ios/inputParser/AttributedStringBuilder.mm | 2 +-
ios/inputParser/HtmlHandler.mm | 24 +++++++++++-----------
ios/inputParser/InputParser.mm | 13 ++++++++++--
4 files changed, 34 insertions(+), 25 deletions(-)
diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm
index 4fab52799..d95445034 100644
--- a/ios/EnrichedTextInputView.mm
+++ b/ios/EnrichedTextInputView.mm
@@ -559,20 +559,20 @@ - (void)updateProps:(Props::Shared const &)props
newViewProps.defaultValue != oldViewProps.defaultValue;
if (stylePropChanged) {
- // all the text needs to be rebuilt
- // we get the current html using old config, then switch to new config and
- // replace text using the html this way, the newest config attributes are
- // being used!
-
- // the html needs to be generated using the old config
- NSString *currentHtml = [parser
- parseToHtmlFromRange:NSMakeRange(0,
- textView.textStorage.string.length)];
-
// now set the new config
config = newConfig;
+
// we already applied html with styles in default value
if (!defaultValueChanged) {
+ // all the text needs to be rebuilt
+ // we get the current html using old config, then switch to new config and
+ // replace text using the html this way, the newest config attributes are
+ // being used!
+
+ // the html needs to be generated using the old config
+ NSString *currentHtml = [parser
+ parseToHtmlFromRange:NSMakeRange(0,
+ textView.textStorage.string.length)];
// no emitting during styles reload
blockEmitting = YES;
diff --git a/ios/inputParser/AttributedStringBuilder.mm b/ios/inputParser/AttributedStringBuilder.mm
index 96a2532d7..9e44398dc 100644
--- a/ios/inputParser/AttributedStringBuilder.mm
+++ b/ios/inputParser/AttributedStringBuilder.mm
@@ -47,7 +47,7 @@ - (void)apply:(NSArray *)processedStyles
range:range
imageData:pair.styleValue];
} else {
- [style addAttributesInAttributedString:attributedString range:r];
+ [style addAttributesInAttributedString:attributedString range:range];
}
}
diff --git a/ios/inputParser/HtmlHandler.mm b/ios/inputParser/HtmlHandler.mm
index 74846e904..b39af4a96 100644
--- a/ios/inputParser/HtmlHandler.mm
+++ b/ios/inputParser/HtmlHandler.mm
@@ -48,7 +48,7 @@ - (NSMutableArray *)convertTagsToStyles:(NSArray *)initiallyProcessedTags {
- (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html {
NSString *fixedHtml = nullptr;
- if (html.length >= MIN_HTML_SIZE) {
+ if (html.length >= 13) {
NSString *firstSix = [html substringWithRange:NSMakeRange(0, 6)];
NSString *lastSeven =
[html substringWithRange:NSMakeRange(html.length - 7, 7)];
@@ -57,16 +57,16 @@ - (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html {
[lastSeven isEqualToString:@""]) {
// remove html tags, might be with newlines or without them
fixedHtml = [html copy];
- NSRegularExpression *regex = [NSRegularExpression
- regularExpressionWithPattern:@"\\n?|\\n?"
- options:0
- error:nil];
-
- fixedHtml =
- [regex stringByReplacingMatchesInString:html
- options:0
- range:NSMakeRange(0, html.length)
- withTemplate:@""];
+ // firstly remove newlined html tags if any:
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"\n"
+ withString:@""];
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"\n"
+ withString:@""];
+ // fallback; remove html tags without their newlines
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@""
+ withString:@""];
+ fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@""
+ withString:@""];
} else {
// in other case we are most likely working with some external html - try
// getting the styles from between body tags
@@ -83,7 +83,7 @@ - (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html {
}
// second processing - try fixing htmls with wrong newlines' setup
- if (fixedHtml) {
+ if (fixedHtml != nullptr) {
// add tag wherever needed
fixedHtml = [fixedHtml stringByReplacingOccurrencesOfString:@"
"
withString:@" "];
diff --git a/ios/inputParser/InputParser.mm b/ios/inputParser/InputParser.mm
index 4497931ac..0d21959bf 100644
--- a/ios/inputParser/InputParser.mm
+++ b/ios/inputParser/InputParser.mm
@@ -36,6 +36,11 @@ - (void)replaceWholeFromHtml:(NSString *)html
ConvertHtmlToPlainTextAndStylesResult *plainTextAndStyles =
[_htmlHandler getTextAndStylesFromHtml:html];
+ if (plainTextAndStyles.text == nil) {
+ _input->textView.text = @"";
+ return;
+ }
+
NSMutableAttributedString *attributedString =
[[NSMutableAttributedString alloc]
initWithString:plainTextAndStyles.text
@@ -56,9 +61,15 @@ - (void)replaceWholeFromHtml:(NSString *)html
}
- (void)replaceFromHtml:(NSString *)html range:(NSRange)range {
+ NSTextStorage *storage = _input->textView.textStorage;
ConvertHtmlToPlainTextAndStylesResult *plainTextAndStyles =
[_htmlHandler getTextAndStylesFromHtml:html];
+ if (plainTextAndStyles.text == nil) {
+ [storage replaceCharactersInRange:range withString:@""];
+ return;
+ }
+
NSMutableAttributedString *inserted = [[NSMutableAttributedString alloc]
initWithString:plainTextAndStyles.text
attributes:_input->defaultTypingAttributes];
@@ -68,8 +79,6 @@ - (void)replaceFromHtml:(NSString *)html range:(NSRange)range {
offsetFromBeginning:0
conflictingStyles:_input->conflictingStyles];
- NSTextStorage *storage = _input->textView.textStorage;
-
if (range.location > storage.length)
range.location = storage.length;
if (NSMaxRange(range) > storage.length) {
From c007defae3fa1656a748831ac31ea99e8537ccfd Mon Sep 17 00:00:00 2001
From: IvanIhnatsiuk
Date: Thu, 11 Dec 2025 22:27:26 +0100
Subject: [PATCH 6/8] fix: compiler warnings
---
example/src/App.tsx | 71 ++++++++++++++++++++++++++++++++++++
ios/EnrichedTextInputView.h | 4 --
ios/EnrichedTextInputView.mm | 2 +-
3 files changed, 72 insertions(+), 5 deletions(-)
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 793905609..06d4a5285 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -70,6 +70,77 @@ const DEBUG_SCROLLABLE = false;
// See: https://github.com/software-mansion/react-native-enriched/issues/229
const ANDROID_EXPERIMENTAL_SYNCHRONOUS_EVENTS = false;
+const generateHugeHtml = (repeat = 200) => {
+ const parts: string[] = [];
+ parts.push('');
+
+ // small helper to make deterministic colors
+ const colorAt = (i: number) => {
+ const r = (37 * (i + 1)) % 256;
+ const g = (83 * (i + 7)) % 256;
+ const b = (199 * (i + 13)) % 256;
+ const toHex = (n: number) => n.toString(16).padStart(2, '0');
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
+ };
+
+ for (let i = 0; i < repeat; i++) {
+ const col = colorAt(i);
+ const imgW = 200 + (i % 5) * 40;
+ const imgH = 100 + (i % 3) * 30;
+
+ parts.push(
+ // Headings
+ `\nSection ${i + 1} `,
+ `\nSubsection ${i + 1}.1 `,
+ `\nTopic ${i + 1}.1.a `,
+
+ // Paragraph with mixed inline styles
+ `\nThis is a bold and italic paragraph with underline , ` +
+ `strike , inline_code_${i}, ` +
+ `a link ${i} , ` +
+ ` , ` +
+ ` , ` +
+ `colored text ${col} and some plain text to bulk it up.
`,
+
+ // Line break
+ `\n `,
+
+ // Unordered list
+ ``,
+ `bullet A ${i} `,
+ `bullet B ${i} `,
+ `bullet C ${i} `,
+ ` `,
+
+ // Ordered list
+ `\n`,
+ `\nstep 1.${i} `,
+ `\nstep 2.${i} `,
+ `\nstep 3.${i} `,
+ `\n `,
+
+ // Blockquote
+ `\n`,
+ `\n“Blockquote line 1 for ${i}.”
`,
+ `\n“Blockquote line 2 for ${i}.”
`,
+ `\n `,
+
+ // Code block (escaped characters)
+ `\n`,
+ `\nfor (let k = 0; k < ${i % 7}; k++) { console.log("block_${i}"); }
`,
+ `\n `,
+
+ // Image (self-closing)
+ `\n
`
+ );
+ }
+
+ parts.push('\n');
+ return parts.join('');
+};
+
+const initialHugeHtml = generateHugeHtml();
+
export default function App() {
const [isChannelPopupOpen, setIsChannelPopupOpen] = useState(false);
const [isUserPopupOpen, setIsUserPopupOpen] = useState(false);
diff --git a/ios/EnrichedTextInputView.h b/ios/EnrichedTextInputView.h
index 74a4fd7a1..9bf2c3dd8 100644
--- a/ios/EnrichedTextInputView.h
+++ b/ios/EnrichedTextInputView.h
@@ -31,10 +31,6 @@ NS_ASSUME_NONNULL_BEGIN
@public
BOOL blockEmitting;
}
-@property(nonatomic, strong)
- NSDictionary *> *conflictingStyles;
-@property(nonatomic, strong)
- NSDictionary *> *blockingStyles;
- (CGSize)measureSize:(CGFloat)maxWidth;
- (void)emitOnLinkDetectedEvent:(NSString *)text
url:(NSString *)url
diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm
index d95445034..1647cd3e0 100644
--- a/ios/EnrichedTextInputView.mm
+++ b/ios/EnrichedTextInputView.mm
@@ -1460,8 +1460,8 @@ - (void)didMoveToWindow {
if (self.window && !_didRunInitialMount) {
_didRunInitialMount = YES;
-
[self layoutIfNeeded];
+ // Ideally we should remove this to match RN's uncontrolled inputs behaviour
[self anyTextMayHaveBeenModified];
}
}
From a8ae3f2f633493429bf32e5ea5d878df2da89772 Mon Sep 17 00:00:00 2001
From: IvanIhnatsiuk
Date: Thu, 11 Dec 2025 22:43:17 +0100
Subject: [PATCH 7/8] fix: remove huge html generation
---
example/src/App.tsx | 73 ---------------------------------------------
1 file changed, 73 deletions(-)
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 06d4a5285..d36ac903a 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -70,77 +70,6 @@ const DEBUG_SCROLLABLE = false;
// See: https://github.com/software-mansion/react-native-enriched/issues/229
const ANDROID_EXPERIMENTAL_SYNCHRONOUS_EVENTS = false;
-const generateHugeHtml = (repeat = 200) => {
- const parts: string[] = [];
- parts.push('');
-
- // small helper to make deterministic colors
- const colorAt = (i: number) => {
- const r = (37 * (i + 1)) % 256;
- const g = (83 * (i + 7)) % 256;
- const b = (199 * (i + 13)) % 256;
- const toHex = (n: number) => n.toString(16).padStart(2, '0');
- return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
- };
-
- for (let i = 0; i < repeat; i++) {
- const col = colorAt(i);
- const imgW = 200 + (i % 5) * 40;
- const imgH = 100 + (i % 3) * 30;
-
- parts.push(
- // Headings
- `\nSection ${i + 1} `,
- `\nSubsection ${i + 1}.1 `,
- `\nTopic ${i + 1}.1.a `,
-
- // Paragraph with mixed inline styles
- `\nThis is a bold and italic paragraph with underline , ` +
- `strike , inline_code_${i}, ` +
- `a link ${i} , ` +
- ` , ` +
- ` , ` +
- `colored text ${col} and some plain text to bulk it up.
`,
-
- // Line break
- `\n `,
-
- // Unordered list
- ``,
- `bullet A ${i} `,
- `bullet B ${i} `,
- `bullet C ${i} `,
- ` `,
-
- // Ordered list
- `\n`,
- `\nstep 1.${i} `,
- `\nstep 2.${i} `,
- `\nstep 3.${i} `,
- `\n `,
-
- // Blockquote
- `\n`,
- `\n“Blockquote line 1 for ${i}.”
`,
- `\n“Blockquote line 2 for ${i}.”
`,
- `\n `,
-
- // Code block (escaped characters)
- `\n`,
- `\nfor (let k = 0; k < ${i % 7}; k++) { console.log("block_${i}"); }
`,
- `\n `,
-
- // Image (self-closing)
- `\n
`
- );
- }
-
- parts.push('\n');
- return parts.join('');
-};
-
-const initialHugeHtml = generateHugeHtml();
-
export default function App() {
const [isChannelPopupOpen, setIsChannelPopupOpen] = useState(false);
const [isUserPopupOpen, setIsUserPopupOpen] = useState(false);
@@ -153,7 +82,6 @@ export default function App() {
const [stylesState, setStylesState] = useState(DEFAULT_STYLE);
const [currentLink, setCurrentLink] =
useState(DEFAULT_LINK_STATE);
- const [visible, setVisible] = useState(false);
const ref = useRef(null);
@@ -359,7 +287,6 @@ export default function App() {
style={styles.container}
contentContainerStyle={styles.content}
>
- setVisible(!visible)} />
Enriched Text Input
Date: Fri, 12 Dec 2025 13:47:50 +0100
Subject: [PATCH 8/8] fix: cleanup
---
ios/utils/OccurenceUtils.mm | 10 ----------
ios/utils/TextInsertionUtils.mm | 1 -
2 files changed, 11 deletions(-)
diff --git a/ios/utils/OccurenceUtils.mm b/ios/utils/OccurenceUtils.mm
index 711abd73d..19d53f34f 100644
--- a/ios/utils/OccurenceUtils.mm
+++ b/ios/utils/OccurenceUtils.mm
@@ -65,10 +65,6 @@ + (void)enumerateAttributes:(NSArray *)keys
return result;
}
-#pragma mark - ============================================================
-#pragma mark Public API (Attributed String Versions)
-#pragma mark - ============================================================
-
+ (BOOL)detect:(NSAttributedStringKey)key
inString:(NSAttributedString *)string
inRange:(NSRange)range
@@ -170,11 +166,6 @@ + (BOOL)anyMultiple:(NSArray *)keys
withCondition:condition];
}
-#pragma mark - ============================================================
-#pragma mark Public API (EnrichedTextInputView Versions)
-#pragma mark - ============================================================
-
-/// detects on a range using input->textView.textStorage
+ (BOOL)detect:(NSAttributedStringKey)key
withInput:(EnrichedTextInputView *)input
inRange:(NSRange)range
@@ -185,7 +176,6 @@ + (BOOL)detect:(NSAttributedStringKey)key
withCondition:condition];
}
-/// detects at index (typing attributes logic preserved)
+ (BOOL)detect:(NSAttributedStringKey)key
withInput:(EnrichedTextInputView *)input
atIndex:(NSUInteger)index
diff --git a/ios/utils/TextInsertionUtils.mm b/ios/utils/TextInsertionUtils.mm
index 87d3cb8fe..4c67ef638 100644
--- a/ios/utils/TextInsertionUtils.mm
+++ b/ios/utils/TextInsertionUtils.mm
@@ -84,7 +84,6 @@ + (void)insertTextInAttributedString:(NSString *)text
NSAttributedString *newAttrString =
[[NSAttributedString alloc] initWithString:text attributes:attrs];
- // 4. Insert into the parent attributed string
[attributedString insertAttributedString:newAttrString atIndex:index];
}