From 1eba8456766c4155bf72d3f630685a1a16006b7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Mon, 17 Nov 2025 10:04:00 +0100 Subject: [PATCH 01/15] feat: add support for codeblock on iOS --- ios/EnrichedTextInputView.mm | 61 +++++-- ios/config/InputConfig.h | 6 + ios/config/InputConfig.mm | 30 ++++ ios/styles/CodeBlockStyle.mm | 248 ++++++++++++++++++++++++++++ ios/utils/LayoutManagerExtension.mm | 65 ++++++++ ios/utils/StyleHeaders.h | 4 + ios/utils/ZeroWidthSpaceUtils.mm | 17 +- 7 files changed, 410 insertions(+), 21 deletions(-) create mode 100644 ios/styles/CodeBlockStyle.mm diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index c4882e1c..e3d49d13 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -94,7 +94,8 @@ - (void)setDefaults { @([H3Style getStyleType]): [[H3Style alloc] initWithInput:self], @([UnorderedListStyle getStyleType]): [[UnorderedListStyle alloc] initWithInput:self], @([OrderedListStyle getStyleType]): [[OrderedListStyle alloc] initWithInput:self], - @([BlockQuoteStyle getStyleType]): [[BlockQuoteStyle alloc] initWithInput:self] + @([BlockQuoteStyle getStyleType]): [[BlockQuoteStyle alloc] initWithInput:self], + @([CodeBlockStyle getStyleType]): [[CodeBlockStyle alloc] initWithInput:self] }; _conflictingStyles = @{ @@ -105,28 +106,31 @@ - (void)setDefaults { @([InlineCodeStyle getStyleType]) : @[@([LinkStyle getStyleType]), @([MentionStyle getStyleType])], @([LinkStyle getStyleType]): @[@([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType]), @([MentionStyle getStyleType])], @([MentionStyle getStyleType]): @[@([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType])], - @([H1Style getStyleType]): @[@([H2Style getStyleType]), @([H3Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], - @([H2Style getStyleType]): @[@([H1Style getStyleType]), @([H3Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], - @([H3Style getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], - @([UnorderedListStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], - @([OrderedListStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([UnorderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType])], - @([BlockQuoteStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType])] + @([H1Style getStyleType]): @[@([H2Style getStyleType]), @([H3Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]), @([CodeBlockStyle getStyleType])], + @([H2Style getStyleType]): @[@([H1Style getStyleType]), @([H3Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]), @([CodeBlockStyle getStyleType])], + @([H3Style getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]), @([CodeBlockStyle getStyleType])], + @([UnorderedListStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]), @([CodeBlockStyle getStyleType])], + @([OrderedListStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([UnorderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]), @([CodeBlockStyle getStyleType])], + @([BlockQuoteStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([CodeBlockStyle getStyleType])], + @([CodeBlockStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), + @([BoldStyle getStyleType]), @([ItalicStyle getStyleType]), @([UnderlineStyle getStyleType]), @([StrikethroughStyle getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]), @([InlineCodeStyle getStyleType])] }; _blockingStyles = @{ - @([BoldStyle getStyleType]) : @[], - @([ItalicStyle getStyleType]) : @[], - @([UnderlineStyle getStyleType]) : @[], - @([StrikethroughStyle getStyleType]) : @[], - @([InlineCodeStyle getStyleType]) : @[], - @([LinkStyle getStyleType]): @[], - @([MentionStyle getStyleType]): @[], + @([BoldStyle getStyleType]) : @[@([CodeBlockStyle getStyleType])], + @([ItalicStyle getStyleType]) : @[@([CodeBlockStyle getStyleType])], + @([UnderlineStyle getStyleType]) : @[@([CodeBlockStyle getStyleType])], + @([StrikethroughStyle getStyleType]) : @[@([CodeBlockStyle getStyleType])], + @([InlineCodeStyle getStyleType]) : @[@([CodeBlockStyle getStyleType])], + @([LinkStyle getStyleType]): @[@([CodeBlockStyle getStyleType])], + @([MentionStyle getStyleType]): @[@([CodeBlockStyle getStyleType])], @([H1Style getStyleType]): @[], @([H2Style getStyleType]): @[], @([H3Style getStyleType]): @[], @([UnorderedListStyle getStyleType]): @[], @([OrderedListStyle getStyleType]): @[], @([BlockQuoteStyle getStyleType]): @[], + @([CodeBlockStyle getStyleType]): @[], }; parser = [[InputParser alloc] initWithInput:self]; @@ -346,6 +350,25 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & } } + if(newViewProps.htmlStyle.codeblock.color != oldViewProps.htmlStyle.codeblock.color) { + if(isColorMeaningful(newViewProps.htmlStyle.codeblock.color)) { + [newConfig setCodeBlockFgColor:RCTUIColorFromSharedColor(newViewProps.htmlStyle.codeblock.color)]; + stylePropChanged = YES; + } + } + + if(newViewProps.htmlStyle.codeblock.backgroundColor != oldViewProps.htmlStyle.codeblock.backgroundColor) { + if(isColorMeaningful(newViewProps.htmlStyle.codeblock.backgroundColor)) { + [newConfig setCodeBlockBgColor:RCTUIColorFromSharedColor(newViewProps.htmlStyle.codeblock.backgroundColor)]; + stylePropChanged = YES; + } + } + + if(newViewProps.htmlStyle.codeblock.borderRadius != oldViewProps.htmlStyle.codeblock.borderRadius) { + [newConfig setCodeBlockBorderRadius:newViewProps.htmlStyle.codeblock.borderRadius]; + stylePropChanged = YES; + } + if(newViewProps.htmlStyle.a.textDecorationLine != oldViewProps.htmlStyle.a.textDecorationLine) { NSString *objcString = [NSString fromCppString:newViewProps.htmlStyle.a.textDecorationLine]; if([objcString isEqualToString:DecorationUnderline]) { @@ -696,7 +719,7 @@ - (void)tryUpdatingActiveStyles { .isUnorderedList = [_activeStyles containsObject: @([UnorderedListStyle getStyleType])], .isOrderedList = [_activeStyles containsObject: @([OrderedListStyle getStyleType])], .isBlockQuote = [_activeStyles containsObject: @([BlockQuoteStyle getStyleType])], - .isCodeBlock = NO, // [_activeStyles containsObject: @([CodeBlockStyle getStyleType])], + .isCodeBlock = [_activeStyles containsObject: @([CodeBlockStyle getStyleType])], .isImage = NO // [_activeStyles containsObject: @([ImageStyle getStyleType]])], }); } @@ -768,6 +791,8 @@ - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args { [self toggleParagraphStyle:[OrderedListStyle getStyleType]]; } else if([commandName isEqualToString:@"toggleBlockQuote"]) { [self toggleParagraphStyle:[BlockQuoteStyle getStyleType]]; + } else if([commandName isEqualToString:@"toggleCodeBlock"]) { + [self toggleParagraphStyle:[CodeBlockStyle getStyleType]]; } } @@ -1033,6 +1058,12 @@ - (void)anyTextMayHaveBeenModified { [bqStyle manageBlockquoteColor]; } + // codeblock colors management + CodeBlockStyle *codeBlockStyle = stylesDict[@([CodeBlockStyle getStyleType])]; + if(codeBlockStyle != nullptr) { + [codeBlockStyle manageCodeBlockColor]; + } + // improper headings fix H1Style *h1Style = stylesDict[@([H1Style getStyleType])]; H2Style *h2Style = stylesDict[@([H2Style getStyleType])]; diff --git a/ios/config/InputConfig.h b/ios/config/InputConfig.h index cd12910c..4778807c 100644 --- a/ios/config/InputConfig.h +++ b/ios/config/InputConfig.h @@ -64,4 +64,10 @@ - (void)setLinkDecorationLine:(TextDecorationLineEnum)newValue; - (void)setMentionStyleProps:(NSDictionary *)newValue; - (MentionStyleProps *)mentionStylePropsForIndicator:(NSString *)indicator; +- (UIColor *)codeBlockFgColor; +- (void)setCodeBlockFgColor:(UIColor *)newValue; +- (UIColor *)codeBlockBgColor; +- (void)setCodeBlockBgColor:(UIColor *)newValue; +- (CGFloat)codeBlockBorderRadius; +- (void)setCodeBlockBorderRadius:(CGFloat)newValue; @end diff --git a/ios/config/InputConfig.mm b/ios/config/InputConfig.mm index 00f0903c..10dbff10 100644 --- a/ios/config/InputConfig.mm +++ b/ios/config/InputConfig.mm @@ -36,6 +36,9 @@ @implementation InputConfig { UIColor *_linkColor; TextDecorationLineEnum _linkDecorationLine; NSDictionary *_mentionProperties; + UIColor *_codeBlockFgColor; + CGFloat _codeBlockBorderRadius; + UIColor *_codeBlockBgColor; } - (instancetype) init { @@ -79,6 +82,9 @@ - (id)copyWithZone:(NSZone *)zone { copy->_linkColor = [_linkColor copy]; copy->_linkDecorationLine = [_linkDecorationLine copy]; copy->_mentionProperties = [_mentionProperties mutableCopy]; + copy->_codeBlockFgColor = [_codeBlockFgColor copy]; + copy->_codeBlockBgColor = [_codeBlockBgColor copy]; + copy->_codeBlockBorderRadius = _codeBlockBorderRadius; return copy; } @@ -379,4 +385,28 @@ - (MentionStyleProps *)mentionStylePropsForIndicator:(NSString *)indicator { return fallbackProps; } +- (UIColor *)codeBlockFgColor { + return _codeBlockFgColor; +} + +- (void)setCodeBlockFgColor:(UIColor *)newValue { + _codeBlockFgColor = newValue; +} + +- (UIColor *)codeBlockBgColor { + return _codeBlockBgColor; +} + +- (void)setCodeBlockBgColor:(UIColor *)newValue { + _codeBlockBgColor = newValue; +} + +- (CGFloat)codeBlockBorderRadius { + return _codeBlockBorderRadius; +} + +- (void)setCodeBlockBorderRadius:(CGFloat)newValue { + _codeBlockBorderRadius = newValue; +} + @end diff --git a/ios/styles/CodeBlockStyle.mm b/ios/styles/CodeBlockStyle.mm new file mode 100644 index 00000000..88fdec42 --- /dev/null +++ b/ios/styles/CodeBlockStyle.mm @@ -0,0 +1,248 @@ +#import "StyleHeaders.h" +#import "EnrichedTextInputView.h" +#import "FontExtension.h" +#import "OccurenceUtils.h" +#import "ParagraphsUtils.h" +#import "TextInsertionUtils.h" +#import "ColorExtension.h" + +@implementation CodeBlockStyle { + EnrichedTextInputView *_input; +} + ++ (StyleType)getStyleType { return CodeBlock; } + +- (instancetype)initWithInput:(id)input { + self = [super init]; + _input = (EnrichedTextInputView *)input; + return self; +} + +- (void)applyStyle:(NSRange)range { + BOOL isStylePresent = [self detectStyle:range]; + if(range.length >= 1) { + isStylePresent ? [self removeAttributes:range] : [self addAttributes:range]; + } else { + isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes]; + } +} + +- (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; + + // to not emit any space filling selection/text changes + _input->blockEmitting = YES; + + for (NSValue *value in paragraphs) { + NSRange pRange = NSMakeRange([value rangeValue].location + offset, [value rangeValue].length); + // length 0 with first line, length 1 and newline with some empty lines in the middle + if(pRange.length == 0 || + (pRange.length == 1 && + [[NSCharacterSet newlineCharacterSet] characterIsMember: [_input->textView.textStorage.string characterAtIndex:pRange.location]]) + ) { + [TextInsertionUtils insertText:@"\u200B" at:pRange.location additionalAttributes:nullptr input:_input withSelection:NO]; + pRange = NSMakeRange(pRange.location, pRange.length + 1); + offset += 1; + } + + [_input->textView.textStorage enumerateAttribute:NSFontAttributeName + inRange:pRange + 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]; + } + }]; + + [_input->textView.textStorage enumerateAttribute:NSParagraphStyleAttributeName inRange:pRange options:0 + usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { + NSMutableParagraphStyle *pStyle = [(NSParagraphStyle *)value mutableCopy]; + pStyle.textLists = @[codeBlockList]; + [_input->textView.textStorage addAttribute:NSParagraphStyleAttributeName value:pStyle range:range]; + } + ]; + } + + // back to emitting + _input->blockEmitting = NO; + + if(preModificationRange.length == 0) { + // fix selection if only one line was possibly made a list and filled with a space + _input->textView.selectedRange = preModificationRange; + } else { + // in other cases, fix the selection with newly made offsets + _input->textView.selectedRange = NSMakeRange(preModificationRange.location, preModificationRange.length + offset); + } + + // also add typing attributes + NSMutableDictionary *typingAttrs = [_input->textView.typingAttributes mutableCopy]; + NSMutableParagraphStyle *pStyle = [typingAttrs[NSParagraphStyleAttributeName] mutableCopy]; + pStyle.textLists = @[codeBlockList]; + typingAttrs[NSParagraphStyleAttributeName] = pStyle; + UIFont *currentFont = typingAttrs[NSFontAttributeName]; + if(currentFont != nullptr) { + typingAttrs[NSFontAttributeName] = [[[_input->config monospacedFont] withFontTraits:currentFont] setSize:currentFont.pointSize]; + } + + _input->textView.typingAttributes = typingAttrs; +} + +- (void)addTypingAttributes { + [self addAttributes:_input->textView.selectedRange]; +} + +- (void)removeAttributes:(NSRange)range { + NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView range:range]; + + [_input->textView.textStorage beginEditing]; + + for(NSValue *value in paragraphs) { + NSRange pRange = [value rangeValue]; + [_input->textView.textStorage enumerateAttribute:NSFontAttributeName inRange:pRange options:0 + usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { + UIFont *font = (UIFont *)value; + if(font != nullptr) { + UIFont *newFont = [[[_input->config primaryFont] withFontTraits:font] setSize:font.pointSize]; + [_input->textView.textStorage addAttribute:NSFontAttributeName value:newFont range:range]; + } + } + ]; + + [_input->textView.textStorage enumerateAttribute:NSParagraphStyleAttributeName inRange:range options:0 + usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { + NSMutableParagraphStyle *pStyle = [(NSParagraphStyle *)value mutableCopy]; + pStyle.textLists = @[]; + [_input->textView.textStorage addAttribute:NSParagraphStyleAttributeName value:pStyle range:range]; + } + ]; + } + + [_input->textView.textStorage endEditing]; + + // also remove typing attributes + NSMutableDictionary *typingAttrs = [_input->textView.typingAttributes mutableCopy]; + NSMutableParagraphStyle *pStyle = [typingAttrs[NSParagraphStyleAttributeName] mutableCopy]; + pStyle.textLists = @[]; + + typingAttrs[NSParagraphStyleAttributeName] = pStyle; + UIFont *currentFont = typingAttrs[NSFontAttributeName]; + if(currentFont != nullptr) { + typingAttrs[NSFontAttributeName] = [[[_input->config primaryFont] withFontTraits:currentFont] setSize:currentFont.pointSize]; + } + + _input->textView.typingAttributes = typingAttrs; +} + +- (void)removeTypingAttributes { + [self removeAttributes:_input->textView.selectedRange]; +} + +- (BOOL)styleCondition:(id _Nullable)value :(NSRange)range { + NSParagraphStyle *paragraph = (NSParagraphStyle *)value; + return paragraph != nullptr && paragraph.textLists.count == 1 && [paragraph.textLists.firstObject.markerFormat isEqual: @"codeblock"]; +} + +- (BOOL)detectStyle:(NSRange)range { + if(range.length >= 1) { + return [OccurenceUtils detect:NSParagraphStyleAttributeName withInput:_input inRange:range + withCondition: ^BOOL(id _Nullable value, NSRange range) { + return [self styleCondition:value :range]; + } + ]; + } else { + return [OccurenceUtils detect:NSParagraphStyleAttributeName withInput:_input atIndex:range.location checkPrevious:YES + withCondition:^BOOL(id _Nullable value, NSRange range) { + return [self styleCondition:value :range]; + } + ]; + } +} + +- (BOOL)anyOccurence:(NSRange)range { + return [OccurenceUtils any:NSParagraphStyleAttributeName withInput:_input inRange:range + withCondition:^BOOL(id _Nullable value, NSRange range) { + return [self styleCondition:value :range]; + } + ]; +} + +- (NSArray *_Nullable)findAllOccurences:(NSRange)range { + return [OccurenceUtils all:NSParagraphStyleAttributeName withInput:_input inRange:range + withCondition:^BOOL(id _Nullable value, NSRange range) { + return [self styleCondition:value :range]; + } + ]; +} + +// gets ranges that aren't link, mention or inline code +- (NSArray *)getProperColorRangesIn:(NSRange)range { + LinkStyle *linkStyle = _input->stylesDict[@([LinkStyle getStyleType])]; + MentionStyle *mentionStyle = _input->stylesDict[@([MentionStyle getStyleType])]; + InlineCodeStyle *codeStyle = _input->stylesDict[@([InlineCodeStyle getStyleType])]; + + NSMutableArray *newRanges = [[NSMutableArray alloc] init]; + int lastRangeLocation = range.location; + + for (int i = range.location; i < range.location + range.length; i++) { + NSRange currentRange = NSMakeRange(i, 1); + if ([linkStyle detectStyle:currentRange] || [mentionStyle detectStyle:currentRange] || [codeStyle detectStyle:currentRange]) { + if (i - lastRangeLocation > 0) { + [newRanges addObject:[NSValue valueWithRange:NSMakeRange(lastRangeLocation, i - lastRangeLocation)]]; + } + lastRangeLocation = i+1; + } + } + if (lastRangeLocation < range.location + range.length) { + [newRanges addObject:[NSValue valueWithRange:NSMakeRange(lastRangeLocation, range.location + range.length - lastRangeLocation)]]; + } + + return newRanges; +} + +// general checkup correcting codeBlock color +// since links, mentions and inline code affects coloring, the checkup gets done only outside of them +- (void)manageCodeBlockColor { + if([[_input->config blockquoteColor] isEqualToColor:[_input->config primaryColor]]) { + return; + } + + NSRange wholeRange = NSMakeRange(0, _input->textView.textStorage.string.length); + + NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView range:wholeRange]; + for(NSValue *pValue in paragraphs) { + NSRange paragraphRange = [pValue rangeValue]; + NSArray *properRanges = [self getProperColorRangesIn:paragraphRange]; + + for(NSValue *value in properRanges) { + NSRange currRange = [value rangeValue]; + BOOL selfDetected = [self detectStyle:currRange]; + + [_input->textView.textStorage enumerateAttribute:NSForegroundColorAttributeName inRange:currRange options:0 + usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { + UIColor *newColor = nullptr; + BOOL colorApplied = [(UIColor *)value isEqualToColor:[_input->config codeBlockFgColor]]; + + if(colorApplied && !selfDetected) { + newColor = [_input->config primaryColor]; + } else if(!colorApplied && selfDetected) { + newColor = [_input->config codeBlockFgColor]; + } + + if(newColor != nullptr) { + [_input->textView.textStorage addAttribute:NSForegroundColorAttributeName value:newColor range:currRange]; + [_input->textView.textStorage addAttribute:NSUnderlineColorAttributeName value:newColor range:currRange]; + [_input->textView.textStorage addAttribute:NSStrikethroughColorAttributeName value:newColor range:currRange]; + } + } + ]; + } + } +} + +@end diff --git a/ios/utils/LayoutManagerExtension.mm b/ios/utils/LayoutManagerExtension.mm index 26a9cbcd..6a47ac11 100644 --- a/ios/utils/LayoutManagerExtension.mm +++ b/ios/utils/LayoutManagerExtension.mm @@ -3,6 +3,7 @@ #import "EnrichedTextInputView.h" #import "StyleHeaders.h" #import "ParagraphsUtils.h" +#import "ColorExtension.h" @implementation NSLayoutManager (LayoutManagerExtension) @@ -130,6 +131,70 @@ - (void)my_drawBackgroundForGlyphRange:(NSRange)glyphRange atPoint:(CGPoint)orig ]; } } + + [self drawCodeBlocks:typedInput origin:origin]; +} + +- (void)drawCodeBlocks:(EnrichedTextInputView *)typedInput origin:(CGPoint)origin +{ + NSRange inputRange = NSMakeRange(0, typedInput->textView.textStorage.length); + CodeBlockStyle *codeBlockStyle = typedInput->stylesDict[@([CodeBlockStyle getStyleType])]; + if(codeBlockStyle == nullptr) { return; } + + NSArray *allCodeBlocks = [codeBlockStyle findAllOccurences:inputRange]; + UIColor *bgColor = [[typedInput->config codeBlockBgColor] colorWithAlphaIfNotTransparent:0.4]; + CGFloat radius = [typedInput->config codeBlockBorderRadius]; + [bgColor setFill]; + + for (StylePair *pair in allCodeBlocks) { + NSRange blockCharacterRange = [pair.rangeValue rangeValue]; + if (blockCharacterRange.length == 0) continue; + + NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesIn:typedInput->textView range:blockCharacterRange]; + if (paragraphs.count == 0) continue; + + NSRange firstParagraphRange = [((NSValue *)[paragraphs firstObject]) rangeValue]; + NSRange lastParagraphRange = [((NSValue *)[paragraphs lastObject]) rangeValue]; + + for (NSValue *paragraphValue in paragraphs) { + NSRange paragraphCharacterRange = [paragraphValue rangeValue]; + + BOOL isFirstParagraph = NSEqualRanges(paragraphCharacterRange, firstParagraphRange); + BOOL isLastParagraph = NSEqualRanges(paragraphCharacterRange, lastParagraphRange); + + NSRange paragraphGlyphRange = [self glyphRangeForCharacterRange:paragraphCharacterRange actualCharacterRange:NULL]; + + __block BOOL isFirstLineOfParagraph = YES; + + [self enumerateLineFragmentsForGlyphRange:paragraphGlyphRange + usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) { + + CGRect lineBgRect = rect; + lineBgRect.origin.x = origin.x; + lineBgRect.origin.y += origin.y; + lineBgRect.size.width = textContainer.size.width; + + UIRectCorner cornersForThisLine = 0; + + if (isFirstParagraph && isFirstLineOfParagraph) { + cornersForThisLine = UIRectCornerTopLeft | UIRectCornerTopRight; + } + + BOOL isLastLineOfParagraph = (NSMaxRange(glyphRange) >= NSMaxRange(paragraphGlyphRange)); + + if (isLastParagraph && isLastLineOfParagraph) { + cornersForThisLine = cornersForThisLine | UIRectCornerBottomLeft | UIRectCornerBottomRight; + } + + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:lineBgRect + byRoundingCorners:cornersForThisLine + cornerRadii:CGSizeMake(radius, radius)]; + [path fill]; + + isFirstLineOfParagraph = NO; + }]; + } + } } - (NSString *)markerForList:(NSTextList *)list charIndex:(NSUInteger)index input:(EnrichedTextInputView *)input { diff --git a/ios/utils/StyleHeaders.h b/ios/utils/StyleHeaders.h index 1142a4a6..f420095c 100644 --- a/ios/utils/StyleHeaders.h +++ b/ios/utils/StyleHeaders.h @@ -74,3 +74,7 @@ - (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text; - (void)manageBlockquoteColor; @end + +@interface CodeBlockStyle : NSObject +- (void)manageCodeBlockColor; +@end diff --git a/ios/utils/ZeroWidthSpaceUtils.mm b/ios/utils/ZeroWidthSpaceUtils.mm index 63f76a4a..c02f5037 100644 --- a/ios/utils/ZeroWidthSpaceUtils.mm +++ b/ios/utils/ZeroWidthSpaceUtils.mm @@ -42,9 +42,10 @@ + (void)removeSpacesIfNeededinInput:(EnrichedTextInputView *)input { UnorderedListStyle *ulStyle = input->stylesDict[@([UnorderedListStyle getStyleType])]; OrderedListStyle *olStyle = input->stylesDict[@([OrderedListStyle getStyleType])]; BlockQuoteStyle *bqStyle = input->stylesDict[@([BlockQuoteStyle getStyleType])]; + CodeBlockStyle *cbStyle = input->stylesDict[@([CodeBlockStyle getStyleType])]; // zero width spaces with no lists/blockquote styles on them get removed - if(![ulStyle detectStyle:characterRange] && ![olStyle detectStyle:characterRange] && ![bqStyle detectStyle:characterRange]) { + if(![ulStyle detectStyle:characterRange] && ![olStyle detectStyle:characterRange] && ![bqStyle detectStyle:characterRange] && ![cbStyle detectStyle:characterRange]) { [indexesToBeRemoved addObject:@(characterRange.location)]; } } @@ -76,6 +77,7 @@ + (void)addSpacesIfNeededinInput:(EnrichedTextInputView *)input { UnorderedListStyle *ulStyle = input->stylesDict[@([UnorderedListStyle getStyleType])]; OrderedListStyle *olStyle = input->stylesDict[@([OrderedListStyle getStyleType])]; BlockQuoteStyle *bqStyle = input->stylesDict[@([BlockQuoteStyle getStyleType])]; + CodeBlockStyle *cbStyle = input->stylesDict[@([CodeBlockStyle getStyleType])]; NSMutableArray *indexesToBeInserted = [[NSMutableArray alloc] init]; NSRange preAddSelection = input->textView.selectedRange; @@ -87,7 +89,7 @@ + (void)addSpacesIfNeededinInput:(EnrichedTextInputView *)input { NSRange paragraphRange = [input->textView.textStorage.string paragraphRangeForRange:characterRange]; if(paragraphRange.length == 1) { - if([ulStyle detectStyle:characterRange] || [olStyle detectStyle:characterRange] || [bqStyle detectStyle:characterRange]) { + if([ulStyle detectStyle:characterRange] || [olStyle detectStyle:characterRange] || [bqStyle detectStyle:characterRange] || [cbStyle detectStyle:characterRange]) { // we have an empty list or quote item with no space: add it! [indexesToBeInserted addObject:@(paragraphRange.location)]; } @@ -114,7 +116,7 @@ + (void)addSpacesIfNeededinInput:(EnrichedTextInputView *)input { // additional check for last index of the input NSRange lastRange = NSMakeRange(input->textView.textStorage.string.length, 0); NSRange lastParagraphRange = [input->textView.textStorage.string paragraphRangeForRange:lastRange]; - if(lastParagraphRange.length == 0 && ([ulStyle detectStyle:lastRange] || [olStyle detectStyle:lastRange] || [bqStyle detectStyle:lastRange])) { + if(lastParagraphRange.length == 0 && ([ulStyle detectStyle:lastRange] || [olStyle detectStyle:lastRange] || [bqStyle detectStyle:lastRange] || [cbStyle detectStyle:lastRange])) { [TextInsertionUtils insertText:@"\u200B" at:lastRange.location additionalAttributes:nullptr input:input withSelection:NO]; } @@ -153,13 +155,16 @@ + (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text i UnorderedListStyle *ulStyle = typedInput->stylesDict[@([UnorderedListStyle getStyleType])]; OrderedListStyle *olStyle = typedInput->stylesDict[@([OrderedListStyle getStyleType])]; BlockQuoteStyle *bqStyle = typedInput->stylesDict[@([BlockQuoteStyle getStyleType])]; + CodeBlockStyle *cbStyle = typedInput->stylesDict[@([CodeBlockStyle getStyleType])]; - if([ulStyle detectStyle:styleRemovalRange]) { + if ([ulStyle detectStyle:styleRemovalRange]) { [ulStyle removeAttributes:styleRemovalRange]; - } else if([olStyle detectStyle:styleRemovalRange]) { + } else if ([olStyle detectStyle:styleRemovalRange]) { [olStyle removeAttributes:styleRemovalRange]; - } else if([bqStyle detectStyle:styleRemovalRange]) { + } else if ([bqStyle detectStyle:styleRemovalRange]) { [bqStyle removeAttributes:styleRemovalRange]; + }else if ([cbStyle detectStyle:styleRemovalRange]) { + [cbStyle removeAttributes:styleRemovalRange]; } return YES; From 11515995d2d3fc43e9bc78e5c45580fbc6ebe6c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Mon, 17 Nov 2025 10:29:44 +0100 Subject: [PATCH 02/15] feat: add codeblock handling in parser --- ios/inputParser/InputParser.mm | 35 ++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/ios/inputParser/InputParser.mm b/ios/inputParser/InputParser.mm index f27cecda..22734bf6 100644 --- a/ios/inputParser/InputParser.mm +++ b/ios/inputParser/InputParser.mm @@ -28,6 +28,7 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range { BOOL inUnorderedList = NO; BOOL inOrderedList = NO; BOOL inBlockQuote = NO; + BOOL inCodeBlock = NO; unichar lastCharacter = 0; for(int i = 0; i < text.length; i++) { @@ -94,7 +95,8 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range { [previousActiveStyles containsObject:@([H1Style getStyleType])] || [previousActiveStyles containsObject:@([H2Style getStyleType])] || [previousActiveStyles containsObject:@([H3Style getStyleType])] || - [previousActiveStyles containsObject:@([BlockQuoteStyle getStyleType])] + [previousActiveStyles containsObject:@([BlockQuoteStyle getStyleType])] || + [previousActiveStyles containsObject:@([CodeBlockStyle getStyleType])] ) { // do nothing, proper closing paragraph tags have been already appended } else { @@ -127,6 +129,11 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range { 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])]) { @@ -143,6 +150,11 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range { 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])] || @@ -150,7 +162,8 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range { [currentActiveStyles containsObject:@([H1Style getStyleType])] || [currentActiveStyles containsObject:@([H2Style getStyleType])] || [currentActiveStyles containsObject:@([H3Style getStyleType])] || - [currentActiveStyles containsObject:@([BlockQuoteStyle getStyleType])] + [currentActiveStyles containsObject:@([BlockQuoteStyle getStyleType])] || + [currentActiveStyles containsObject:@([CodeBlockStyle getStyleType])] ) { [result appendString:@"\n"]; } else { @@ -234,6 +247,8 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range { [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])] || @@ -257,6 +272,10 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range { inBlockQuote = NO; [result appendString:@"\n"]; } + if(inCodeBlock) { + inCodeBlock = NO; + [result appendString:@"\n"]; + } } [result appendString: @"\n"]; @@ -327,8 +346,8 @@ - (NSString *)tagContentForStyle:(NSNumber *)style openingTag:(BOOL)openingTag l return @"h3"; } else if([style isEqualToNumber:@([UnorderedListStyle getStyleType])] || [style isEqualToNumber:@([OrderedListStyle getStyleType])]) { return @"li"; - } else if([style isEqualToNumber:@([BlockQuoteStyle getStyleType])]) { - // blockquotes use

tags the same way lists use

  • + } else if([style isEqualToNumber:@([BlockQuoteStyle getStyleType])] || [style isEqualToNumber:@([CodeBlockStyle getStyleType])]) { + // blockquotes and codeblock use

    tags the same way lists use

  • return @"p"; } return @""; @@ -446,6 +465,8 @@ - (NSString * _Nullable)initiallyProcessHtml:(NSString * _Nonnull)html { 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]; @@ -518,14 +539,14 @@ - (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml { 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"]) { + if([currentTagName isEqualToString:@"ul"] || [currentTagName isEqualToString:@"ol"] || [currentTagName isEqualToString:@"blockquote"] || [currentTagName isEqualToString:@"codeblock"]) { i += 1; } } 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"]) { + if([currentTagName isEqualToString:@"ul"] || [currentTagName isEqualToString:@"ol"] || [currentTagName isEqualToString:@"blockquote"] || [currentTagName isEqualToString:@"codeblock"]) { plainText = [[plainText substringWithRange: NSMakeRange(0, plainText.length - 1)] mutableCopy]; } @@ -643,6 +664,8 @@ - (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml { [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; From cd129a29987017f1f5a5e887e59337d05f57c41f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Mon, 17 Nov 2025 12:08:07 +0100 Subject: [PATCH 03/15] fix: remove unnecessary color check --- ios/styles/CodeBlockStyle.mm | 70 ++++++++++-------------------------- 1 file changed, 19 insertions(+), 51 deletions(-) diff --git a/ios/styles/CodeBlockStyle.mm b/ios/styles/CodeBlockStyle.mm index 88fdec42..27881753 100644 --- a/ios/styles/CodeBlockStyle.mm +++ b/ios/styles/CodeBlockStyle.mm @@ -180,35 +180,8 @@ - (BOOL)anyOccurence:(NSRange)range { ]; } -// gets ranges that aren't link, mention or inline code -- (NSArray *)getProperColorRangesIn:(NSRange)range { - LinkStyle *linkStyle = _input->stylesDict[@([LinkStyle getStyleType])]; - MentionStyle *mentionStyle = _input->stylesDict[@([MentionStyle getStyleType])]; - InlineCodeStyle *codeStyle = _input->stylesDict[@([InlineCodeStyle getStyleType])]; - - NSMutableArray *newRanges = [[NSMutableArray alloc] init]; - int lastRangeLocation = range.location; - - for (int i = range.location; i < range.location + range.length; i++) { - NSRange currentRange = NSMakeRange(i, 1); - if ([linkStyle detectStyle:currentRange] || [mentionStyle detectStyle:currentRange] || [codeStyle detectStyle:currentRange]) { - if (i - lastRangeLocation > 0) { - [newRanges addObject:[NSValue valueWithRange:NSMakeRange(lastRangeLocation, i - lastRangeLocation)]]; - } - lastRangeLocation = i+1; - } - } - if (lastRangeLocation < range.location + range.length) { - [newRanges addObject:[NSValue valueWithRange:NSMakeRange(lastRangeLocation, range.location + range.length - lastRangeLocation)]]; - } - - return newRanges; -} - -// general checkup correcting codeBlock color -// since links, mentions and inline code affects coloring, the checkup gets done only outside of them - (void)manageCodeBlockColor { - if([[_input->config blockquoteColor] isEqualToColor:[_input->config primaryColor]]) { + if([[_input->config codeBlockFgColor] isEqualToColor:[_input->config primaryColor]]) { return; } @@ -217,31 +190,26 @@ - (void)manageCodeBlockColor { NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView range:wholeRange]; for(NSValue *pValue in paragraphs) { NSRange paragraphRange = [pValue rangeValue]; - NSArray *properRanges = [self getProperColorRangesIn:paragraphRange]; + BOOL selfDetected = [self detectStyle:paragraphRange]; - for(NSValue *value in properRanges) { - NSRange currRange = [value rangeValue]; - BOOL selfDetected = [self detectStyle:currRange]; - - [_input->textView.textStorage enumerateAttribute:NSForegroundColorAttributeName inRange:currRange options:0 - usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { - UIColor *newColor = nullptr; - BOOL colorApplied = [(UIColor *)value isEqualToColor:[_input->config codeBlockFgColor]]; - - if(colorApplied && !selfDetected) { - newColor = [_input->config primaryColor]; - } else if(!colorApplied && selfDetected) { - newColor = [_input->config codeBlockFgColor]; - } - - if(newColor != nullptr) { - [_input->textView.textStorage addAttribute:NSForegroundColorAttributeName value:newColor range:currRange]; - [_input->textView.textStorage addAttribute:NSUnderlineColorAttributeName value:newColor range:currRange]; - [_input->textView.textStorage addAttribute:NSStrikethroughColorAttributeName value:newColor range:currRange]; - } + [_input->textView.textStorage enumerateAttribute:NSForegroundColorAttributeName inRange:paragraphRange options:0 + usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { + UIColor *newColor = nullptr; + BOOL colorApplied = [(UIColor *)value isEqualToColor:[_input->config codeBlockFgColor]]; + + if(colorApplied && !selfDetected) { + newColor = [_input->config primaryColor]; + } else if(!colorApplied && selfDetected) { + newColor = [_input->config codeBlockFgColor]; } - ]; - } + + if(newColor != nullptr) { + [_input->textView.textStorage addAttribute:NSForegroundColorAttributeName value:newColor range:range]; + [_input->textView.textStorage addAttribute:NSUnderlineColorAttributeName value:newColor range:range]; + [_input->textView.textStorage addAttribute:NSStrikethroughColorAttributeName value:newColor range:range]; + } + } + ]; } } From 9acc42cd5bbf468e2d27803c1e82f2eaaece8ab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Mon, 17 Nov 2025 14:59:15 +0100 Subject: [PATCH 04/15] fix: handle conflicts with automatic links --- ios/EnrichedTextInputView.mm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index e3d49d13..0285ce9a 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -113,7 +113,7 @@ - (void)setDefaults { @([OrderedListStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([UnorderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]), @([CodeBlockStyle getStyleType])], @([BlockQuoteStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([CodeBlockStyle getStyleType])], @([CodeBlockStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType]), - @([BoldStyle getStyleType]), @([ItalicStyle getStyleType]), @([UnderlineStyle getStyleType]), @([StrikethroughStyle getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]), @([InlineCodeStyle getStyleType])] + @([BoldStyle getStyleType]), @([ItalicStyle getStyleType]), @([UnderlineStyle getStyleType]), @([StrikethroughStyle getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]), @([InlineCodeStyle getStyleType]), @([MentionStyle getStyleType]), @([LinkStyle getStyleType])] }; _blockingStyles = @{ @@ -1024,7 +1024,8 @@ - (void)manageSelectionBasedChanges { - (void)handleWordModificationBasedChanges:(NSString*)word inRange:(NSRange)range { // manual links refreshing and automatic links detection handling LinkStyle* linkStyle = [stylesDict objectForKey:@([LinkStyle getStyleType])]; - if(linkStyle != nullptr) { + + if(linkStyle != nullptr && [self handleStyleBlocksAndConflicts:[LinkStyle getStyleType] range:range]) { // manual links need to be handled first because they can block automatic links after being refreshed [linkStyle handleManualLinks:word inRange:range]; [linkStyle handleAutomaticLinks:word inRange:range]; From 7d651964592b2793d029028492630640cb2f1b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Tue, 18 Nov 2025 10:39:19 +0100 Subject: [PATCH 05/15] fix: setting foreground color in codeblock --- ios/EnrichedTextInputView.mm | 10 ++---- ios/styles/CodeBlockStyle.mm | 63 +++++++++++++++++------------------- ios/styles/LinkStyle.mm | 6 ++++ ios/utils/StyleHeaders.h | 1 + 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index 0285ce9a..e57e8a86 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -1025,7 +1025,7 @@ - (void)handleWordModificationBasedChanges:(NSString*)word inRange:(NSRange)rang // manual links refreshing and automatic links detection handling LinkStyle* linkStyle = [stylesDict objectForKey:@([LinkStyle getStyleType])]; - if(linkStyle != nullptr && [self handleStyleBlocksAndConflicts:[LinkStyle getStyleType] range:range]) { + if(linkStyle != nullptr) { // manual links need to be handled first because they can block automatic links after being refreshed [linkStyle handleManualLinks:word inRange:range]; [linkStyle handleAutomaticLinks:word inRange:range]; @@ -1059,12 +1059,6 @@ - (void)anyTextMayHaveBeenModified { [bqStyle manageBlockquoteColor]; } - // codeblock colors management - CodeBlockStyle *codeBlockStyle = stylesDict[@([CodeBlockStyle getStyleType])]; - if(codeBlockStyle != nullptr) { - [codeBlockStyle manageCodeBlockColor]; - } - // improper headings fix H1Style *h1Style = stylesDict[@([H1Style getStyleType])]; H2Style *h2Style = stylesDict[@([H2Style getStyleType])]; @@ -1189,6 +1183,7 @@ - (bool)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range r UnorderedListStyle *uStyle = stylesDict[@([UnorderedListStyle getStyleType])]; OrderedListStyle *oStyle = stylesDict[@([OrderedListStyle getStyleType])]; BlockQuoteStyle *bqStyle = stylesDict[@([BlockQuoteStyle getStyleType])]; + CodeBlockStyle *cbStyle = stylesDict[@([CodeBlockStyle getStyleType])]; LinkStyle *linkStyle = stylesDict[@([LinkStyle getStyleType])]; MentionStyle *mentionStyle = stylesDict[@([MentionStyle getStyleType])]; H1Style *h1Style = stylesDict[@([H1Style getStyleType])]; @@ -1204,6 +1199,7 @@ - (bool)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range r [oStyle handleBackspaceInRange:range replacementText:text] || [oStyle tryHandlingListShorcutInRange:range replacementText:text] || [bqStyle handleBackspaceInRange:range replacementText:text] || + [cbStyle handleBackspaceInRange:range replacementText:text] || [linkStyle handleLeadingLinkReplacement:range replacementText:text] || [mentionStyle handleLeadingMentionReplacement:range replacementText:text] || [h1Style handleNewlinesInRange:range replacementText:text] || diff --git a/ios/styles/CodeBlockStyle.mm b/ios/styles/CodeBlockStyle.mm index 27881753..d9026679 100644 --- a/ios/styles/CodeBlockStyle.mm +++ b/ios/styles/CodeBlockStyle.mm @@ -60,6 +60,12 @@ - (void)addAttributes:(NSRange)range { } }]; + [_input->textView.textStorage enumerateAttribute:NSForegroundColorAttributeName inRange:pRange options:0 + usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { + [_input->textView.textStorage addAttribute:NSForegroundColorAttributeName value:[_input->config codeBlockFgColor] range:range]; + } + ]; + [_input->textView.textStorage enumerateAttribute:NSParagraphStyleAttributeName inRange:pRange options:0 usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { NSMutableParagraphStyle *pStyle = [(NSParagraphStyle *)value mutableCopy]; @@ -85,6 +91,7 @@ - (void)addAttributes:(NSRange)range { NSMutableParagraphStyle *pStyle = [typingAttrs[NSParagraphStyleAttributeName] mutableCopy]; pStyle.textLists = @[codeBlockList]; typingAttrs[NSParagraphStyleAttributeName] = pStyle; + typingAttrs[NSForegroundColorAttributeName] = [_input->config codeBlockFgColor]; UIFont *currentFont = typingAttrs[NSFontAttributeName]; if(currentFont != nullptr) { typingAttrs[NSFontAttributeName] = [[[_input->config monospacedFont] withFontTraits:currentFont] setSize:currentFont.pointSize]; @@ -114,6 +121,12 @@ - (void)removeAttributes:(NSRange)range { } ]; + [_input->textView.textStorage enumerateAttribute:NSForegroundColorAttributeName inRange:pRange options:0 + usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { + [_input->textView.textStorage addAttribute:NSForegroundColorAttributeName value:[_input->config primaryColor] range:range]; + } + ]; + [_input->textView.textStorage enumerateAttribute:NSParagraphStyleAttributeName inRange:range options:0 usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { NSMutableParagraphStyle *pStyle = [(NSParagraphStyle *)value mutableCopy]; @@ -131,6 +144,7 @@ - (void)removeAttributes:(NSRange)range { pStyle.textLists = @[]; typingAttrs[NSParagraphStyleAttributeName] = pStyle; + typingAttrs[NSForegroundColorAttributeName] = [_input->config primaryColor]; UIFont *currentFont = typingAttrs[NSFontAttributeName]; if(currentFont != nullptr) { typingAttrs[NSFontAttributeName] = [[[_input->config primaryFont] withFontTraits:currentFont] setSize:currentFont.pointSize]; @@ -143,6 +157,22 @@ - (void)removeTypingAttributes { [self removeAttributes:_input->textView.selectedRange]; } +- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text { + if([self detectStyle:_input->textView.selectedRange] && text.length == 0) { + // backspace while the style is active + + NSRange paragraphRange = [_input->textView.textStorage.string paragraphRangeForRange:_input->textView.selectedRange]; + + if(NSEqualRanges(_input->textView.selectedRange, NSMakeRange(0, 0))) { + // a backspace on the very first input's line quote + // it doesn't run textVieDidChange so we need to manually remove attributes + [self removeAttributes:paragraphRange]; + return YES; + } + } + return NO; +} + - (BOOL)styleCondition:(id _Nullable)value :(NSRange)range { NSParagraphStyle *paragraph = (NSParagraphStyle *)value; return paragraph != nullptr && paragraph.textLists.count == 1 && [paragraph.textLists.firstObject.markerFormat isEqual: @"codeblock"]; @@ -180,37 +210,4 @@ - (BOOL)anyOccurence:(NSRange)range { ]; } -- (void)manageCodeBlockColor { - if([[_input->config codeBlockFgColor] isEqualToColor:[_input->config primaryColor]]) { - return; - } - - NSRange wholeRange = NSMakeRange(0, _input->textView.textStorage.string.length); - - NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView range:wholeRange]; - for(NSValue *pValue in paragraphs) { - NSRange paragraphRange = [pValue rangeValue]; - BOOL selfDetected = [self detectStyle:paragraphRange]; - - [_input->textView.textStorage enumerateAttribute:NSForegroundColorAttributeName inRange:paragraphRange options:0 - usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { - UIColor *newColor = nullptr; - BOOL colorApplied = [(UIColor *)value isEqualToColor:[_input->config codeBlockFgColor]]; - - if(colorApplied && !selfDetected) { - newColor = [_input->config primaryColor]; - } else if(!colorApplied && selfDetected) { - newColor = [_input->config codeBlockFgColor]; - } - - if(newColor != nullptr) { - [_input->textView.textStorage addAttribute:NSForegroundColorAttributeName value:newColor range:range]; - [_input->textView.textStorage addAttribute:NSUnderlineColorAttributeName value:newColor range:range]; - [_input->textView.textStorage addAttribute:NSStrikethroughColorAttributeName value:newColor range:range]; - } - } - ]; - } -} - @end diff --git a/ios/styles/LinkStyle.mm b/ios/styles/LinkStyle.mm index 82118e96..04bbbcb8 100644 --- a/ios/styles/LinkStyle.mm +++ b/ios/styles/LinkStyle.mm @@ -279,6 +279,7 @@ - (void)manageLinkTypingAttributes { - (void)handleAutomaticLinks:(NSString *)word inRange:(NSRange)wordRange { InlineCodeStyle *inlineCodeStyle = [_input->stylesDict objectForKey:@([InlineCodeStyle getStyleType])]; MentionStyle *mentionStyle = [_input->stylesDict objectForKey:@([MentionStyle getStyleType])]; + CodeBlockStyle *codeBlockStyle = [_input->stylesDict objectForKey:@([CodeBlockStyle getStyleType])]; if (inlineCodeStyle == nullptr || mentionStyle == nullptr) { return; @@ -294,6 +295,11 @@ - (void)handleAutomaticLinks:(NSString *)word inRange:(NSRange)wordRange { return; } + // we don't recognize links among codeblock + if ([codeBlockStyle anyOccurence:wordRange]) { + return; + } + // remove connected different links [self removeConnectedLinksIfNeeded:word range:wordRange]; diff --git a/ios/utils/StyleHeaders.h b/ios/utils/StyleHeaders.h index f420095c..f7d53352 100644 --- a/ios/utils/StyleHeaders.h +++ b/ios/utils/StyleHeaders.h @@ -77,4 +77,5 @@ @interface CodeBlockStyle : NSObject - (void)manageCodeBlockColor; +- (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text; @end From 8ad26ca6dde0f87d40f50582dc6cb5c65a1fa3ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Tue, 18 Nov 2025 11:25:17 +0100 Subject: [PATCH 06/15] fix: merge contiguous code blocks --- ios/utils/LayoutManagerExtension.mm | 184 ++++++++++++++++++---------- 1 file changed, 117 insertions(+), 67 deletions(-) diff --git a/ios/utils/LayoutManagerExtension.mm b/ios/utils/LayoutManagerExtension.mm index 6a47ac11..2218dc53 100644 --- a/ios/utils/LayoutManagerExtension.mm +++ b/ios/utils/LayoutManagerExtension.mm @@ -53,11 +53,122 @@ - (void)my_drawBackgroundForGlyphRange:(NSRange)glyphRange atPoint:(CGPoint)orig EnrichedTextInputView *typedInput = (EnrichedTextInputView *)self.input; if(typedInput == nullptr) { return; } + NSRange inputRange = NSMakeRange(0, typedInput->textView.textStorage.length); + + [self drawBlockQuotes:typedInput origin:origin inputRange:inputRange]; + [self drawLists:typedInput origin:origin inputRange:inputRange]; + [self drawCodeBlocks:typedInput origin:origin inputRange:inputRange]; +} + +- (void)drawCodeBlocks:(EnrichedTextInputView *)typedInput origin:(CGPoint)origin inputRange:(NSRange)inputRange +{ + CodeBlockStyle *codeBlockStyle = typedInput->stylesDict[@([CodeBlockStyle getStyleType])]; + if(codeBlockStyle == nullptr) { return; } + + NSArray *allCodeBlocks = [codeBlockStyle findAllOccurences:inputRange]; + NSArray *mergedCodeBlocks = [self mergeContiguousStylePairs:allCodeBlocks]; + UIColor *bgColor = [[typedInput->config codeBlockBgColor] colorWithAlphaIfNotTransparent:0.4]; + CGFloat radius = [typedInput->config codeBlockBorderRadius]; + [bgColor setFill]; + + for (StylePair *pair in mergedCodeBlocks) { + NSRange blockCharacterRange = [pair.rangeValue rangeValue]; + if (blockCharacterRange.length == 0) continue; + + NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesIn:typedInput->textView range:blockCharacterRange]; + if (paragraphs.count == 0) continue; + + NSRange firstParagraphRange = [((NSValue *)[paragraphs firstObject]) rangeValue]; + NSRange lastParagraphRange = [((NSValue *)[paragraphs lastObject]) rangeValue]; + + for (NSValue *paragraphValue in paragraphs) { + NSRange paragraphCharacterRange = [paragraphValue rangeValue]; + + BOOL isFirstParagraph = NSEqualRanges(paragraphCharacterRange, firstParagraphRange); + BOOL isLastParagraph = NSEqualRanges(paragraphCharacterRange, lastParagraphRange); + + NSRange paragraphGlyphRange = [self glyphRangeForCharacterRange:paragraphCharacterRange actualCharacterRange:NULL]; + + __block BOOL isFirstLineOfParagraph = YES; + + [self enumerateLineFragmentsForGlyphRange:paragraphGlyphRange + usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) { + + CGRect lineBgRect = rect; + lineBgRect.origin.x = origin.x; + lineBgRect.origin.y += origin.y; + lineBgRect.size.width = textContainer.size.width; + + UIRectCorner cornersForThisLine = 0; + + if (isFirstParagraph && isFirstLineOfParagraph) { + cornersForThisLine = UIRectCornerTopLeft | UIRectCornerTopRight; + } + + BOOL isLastLineOfParagraph = (NSMaxRange(glyphRange) >= NSMaxRange(paragraphGlyphRange)); + + if (isLastParagraph && isLastLineOfParagraph) { + cornersForThisLine = cornersForThisLine | UIRectCornerBottomLeft | UIRectCornerBottomRight; + } + + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:lineBgRect + byRoundingCorners:cornersForThisLine + cornerRadii:CGSizeMake(radius, radius)]; + [path fill]; + + isFirstLineOfParagraph = NO; + }]; + } + } +} + +- (NSArray *)mergeContiguousStylePairs:(NSArray *)pairs +{ + if (pairs.count == 0) { + return @[]; + } + + NSMutableArray *mergedPairs = [[NSMutableArray alloc] init]; + StylePair *currentPair = pairs[0]; + NSRange currentRange = [currentPair.rangeValue rangeValue]; + for (NSUInteger i = 1; i < pairs.count; i++) { + StylePair *nextPair = pairs[i]; + NSRange nextRange = [nextPair.rangeValue rangeValue]; + + // The Gap Check: + // NSMaxRange(currentRange) is where the current block ends. + // nextRange.location is where the next block starts. + if (NSMaxRange(currentRange) == nextRange.location) { + // They touch perfectly (no gap). Merge them. + currentRange.length += nextRange.length; + } else { + // There is a gap (indices don't match). + // 1. Save the finished block. + StylePair *mergedPair = [[StylePair alloc] init]; + mergedPair.rangeValue = [NSValue valueWithRange:currentRange]; + mergedPair.styleValue = currentPair.styleValue; + [mergedPairs addObject:mergedPair]; + + // 2. Start a brand new block. + currentPair = nextPair; + currentRange = nextRange; + } + } + + // Add the final block + StylePair *lastPair = [[StylePair alloc] init]; + lastPair.rangeValue = [NSValue valueWithRange:currentRange]; + lastPair.styleValue = currentPair.styleValue; + [mergedPairs addObject:lastPair]; + + return mergedPairs; +} + +- (void)drawBlockQuotes:(EnrichedTextInputView *)typedInput origin:(CGPoint)origin inputRange:(NSRange)inputRange +{ BlockQuoteStyle *bqStyle = typedInput->stylesDict[@([BlockQuoteStyle getStyleType])]; if(bqStyle == nullptr) { return; } - NSRange inputRange = NSMakeRange(0, typedInput->textView.textStorage.length); - // it isn't the most performant but we have to check for all the blockquotes each time and redraw them NSArray *allBlockquotes = [bqStyle findAllOccurences:inputRange]; @@ -79,7 +190,10 @@ - (void)my_drawBackgroundForGlyphRange:(NSRange)glyphRange atPoint:(CGPoint)orig } ]; } - +} + +- (void)drawLists:(EnrichedTextInputView *)typedInput origin:(CGPoint)origin inputRange:(NSRange)inputRange +{ UnorderedListStyle *ulStyle = typedInput->stylesDict[@([UnorderedListStyle getStyleType])]; OrderedListStyle *olStyle = typedInput->stylesDict[@([OrderedListStyle getStyleType])]; if(ulStyle == nullptr || olStyle == nullptr) { return; } @@ -131,70 +245,6 @@ - (void)my_drawBackgroundForGlyphRange:(NSRange)glyphRange atPoint:(CGPoint)orig ]; } } - - [self drawCodeBlocks:typedInput origin:origin]; -} - -- (void)drawCodeBlocks:(EnrichedTextInputView *)typedInput origin:(CGPoint)origin -{ - NSRange inputRange = NSMakeRange(0, typedInput->textView.textStorage.length); - CodeBlockStyle *codeBlockStyle = typedInput->stylesDict[@([CodeBlockStyle getStyleType])]; - if(codeBlockStyle == nullptr) { return; } - - NSArray *allCodeBlocks = [codeBlockStyle findAllOccurences:inputRange]; - UIColor *bgColor = [[typedInput->config codeBlockBgColor] colorWithAlphaIfNotTransparent:0.4]; - CGFloat radius = [typedInput->config codeBlockBorderRadius]; - [bgColor setFill]; - - for (StylePair *pair in allCodeBlocks) { - NSRange blockCharacterRange = [pair.rangeValue rangeValue]; - if (blockCharacterRange.length == 0) continue; - - NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesIn:typedInput->textView range:blockCharacterRange]; - if (paragraphs.count == 0) continue; - - NSRange firstParagraphRange = [((NSValue *)[paragraphs firstObject]) rangeValue]; - NSRange lastParagraphRange = [((NSValue *)[paragraphs lastObject]) rangeValue]; - - for (NSValue *paragraphValue in paragraphs) { - NSRange paragraphCharacterRange = [paragraphValue rangeValue]; - - BOOL isFirstParagraph = NSEqualRanges(paragraphCharacterRange, firstParagraphRange); - BOOL isLastParagraph = NSEqualRanges(paragraphCharacterRange, lastParagraphRange); - - NSRange paragraphGlyphRange = [self glyphRangeForCharacterRange:paragraphCharacterRange actualCharacterRange:NULL]; - - __block BOOL isFirstLineOfParagraph = YES; - - [self enumerateLineFragmentsForGlyphRange:paragraphGlyphRange - usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) { - - CGRect lineBgRect = rect; - lineBgRect.origin.x = origin.x; - lineBgRect.origin.y += origin.y; - lineBgRect.size.width = textContainer.size.width; - - UIRectCorner cornersForThisLine = 0; - - if (isFirstParagraph && isFirstLineOfParagraph) { - cornersForThisLine = UIRectCornerTopLeft | UIRectCornerTopRight; - } - - BOOL isLastLineOfParagraph = (NSMaxRange(glyphRange) >= NSMaxRange(paragraphGlyphRange)); - - if (isLastParagraph && isLastLineOfParagraph) { - cornersForThisLine = cornersForThisLine | UIRectCornerBottomLeft | UIRectCornerBottomRight; - } - - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:lineBgRect - byRoundingCorners:cornersForThisLine - cornerRadii:CGSizeMake(radius, radius)]; - [path fill]; - - isFirstLineOfParagraph = NO; - }]; - } - } } - (NSString *)markerForList:(NSTextList *)list charIndex:(NSUInteger)index input:(EnrichedTextInputView *)input { From 17cf35e47a9676312392c7652117bbc7430f34d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Tue, 18 Nov 2025 14:04:07 +0100 Subject: [PATCH 07/15] fix: setting font and color in codeblock --- ios/EnrichedTextInputView.mm | 6 ++ ios/styles/CodeBlockStyle.mm | 95 ++++++++++++++------------- ios/utils/ParagraphAttributesUtils.mm | 6 ++ ios/utils/StyleHeaders.h | 2 +- 4 files changed, 64 insertions(+), 45 deletions(-) diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index e57e8a86..9675ad35 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -1059,6 +1059,12 @@ - (void)anyTextMayHaveBeenModified { [bqStyle manageBlockquoteColor]; } + // codeblock font and color management + CodeBlockStyle *codeBlockStyle = stylesDict[@([CodeBlockStyle getStyleType])]; + if(codeBlockStyle != nullptr) { + [codeBlockStyle manageCodeBlockFontAndColor]; + } + // improper headings fix H1Style *h1Style = stylesDict[@([H1Style getStyleType])]; H2Style *h2Style = stylesDict[@([H2Style getStyleType])]; diff --git a/ios/styles/CodeBlockStyle.mm b/ios/styles/CodeBlockStyle.mm index d9026679..798afb96 100644 --- a/ios/styles/CodeBlockStyle.mm +++ b/ios/styles/CodeBlockStyle.mm @@ -49,23 +49,6 @@ - (void)addAttributes:(NSRange)range { offset += 1; } - [_input->textView.textStorage enumerateAttribute:NSFontAttributeName - inRange:pRange - 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]; - } - }]; - - [_input->textView.textStorage enumerateAttribute:NSForegroundColorAttributeName inRange:pRange options:0 - usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { - [_input->textView.textStorage addAttribute:NSForegroundColorAttributeName value:[_input->config codeBlockFgColor] range:range]; - } - ]; - [_input->textView.textStorage enumerateAttribute:NSParagraphStyleAttributeName inRange:pRange options:0 usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { NSMutableParagraphStyle *pStyle = [(NSParagraphStyle *)value mutableCopy]; @@ -91,11 +74,6 @@ - (void)addAttributes:(NSRange)range { NSMutableParagraphStyle *pStyle = [typingAttrs[NSParagraphStyleAttributeName] mutableCopy]; pStyle.textLists = @[codeBlockList]; typingAttrs[NSParagraphStyleAttributeName] = pStyle; - typingAttrs[NSForegroundColorAttributeName] = [_input->config codeBlockFgColor]; - UIFont *currentFont = typingAttrs[NSFontAttributeName]; - if(currentFont != nullptr) { - typingAttrs[NSFontAttributeName] = [[[_input->config monospacedFont] withFontTraits:currentFont] setSize:currentFont.pointSize]; - } _input->textView.typingAttributes = typingAttrs; } @@ -111,23 +89,8 @@ - (void)removeAttributes:(NSRange)range { for(NSValue *value in paragraphs) { NSRange pRange = [value rangeValue]; - [_input->textView.textStorage enumerateAttribute:NSFontAttributeName inRange:pRange options:0 - usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { - UIFont *font = (UIFont *)value; - if(font != nullptr) { - UIFont *newFont = [[[_input->config primaryFont] withFontTraits:font] setSize:font.pointSize]; - [_input->textView.textStorage addAttribute:NSFontAttributeName value:newFont range:range]; - } - } - ]; - [_input->textView.textStorage enumerateAttribute:NSForegroundColorAttributeName inRange:pRange options:0 - usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { - [_input->textView.textStorage addAttribute:NSForegroundColorAttributeName value:[_input->config primaryColor] range:range]; - } - ]; - - [_input->textView.textStorage enumerateAttribute:NSParagraphStyleAttributeName inRange:range options:0 + [_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 = @[]; @@ -144,11 +107,6 @@ - (void)removeAttributes:(NSRange)range { pStyle.textLists = @[]; typingAttrs[NSParagraphStyleAttributeName] = pStyle; - typingAttrs[NSForegroundColorAttributeName] = [_input->config primaryColor]; - UIFont *currentFont = typingAttrs[NSFontAttributeName]; - if(currentFont != nullptr) { - typingAttrs[NSFontAttributeName] = [[[_input->config primaryFont] withFontTraits:currentFont] setSize:currentFont.pointSize]; - } _input->textView.typingAttributes = typingAttrs; } @@ -175,7 +133,7 @@ - (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text { - (BOOL)styleCondition:(id _Nullable)value :(NSRange)range { NSParagraphStyle *paragraph = (NSParagraphStyle *)value; - return paragraph != nullptr && paragraph.textLists.count == 1 && [paragraph.textLists.firstObject.markerFormat isEqual: @"codeblock"]; + return paragraph != nullptr && paragraph.textLists.count == 1 && [paragraph.textLists.firstObject.markerFormat isEqual:@"codeblock"]; } - (BOOL)detectStyle:(NSRange)range { @@ -210,4 +168,53 @@ - (BOOL)anyOccurence:(NSRange)range { ]; } +- (void)manageCodeBlockFontAndColor { + if([[_input->config codeBlockFgColor] isEqualToColor:[_input->config primaryColor]]) { + return; + } + + NSRange wholeRange = NSMakeRange(0, _input->textView.textStorage.string.length); + NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView range:wholeRange]; + + for(NSValue *pValue in paragraphs) { + NSRange paragraphRange = [pValue rangeValue]; + BOOL selfDetected = [self detectStyle:paragraphRange]; + + [_input->textView.textStorage enumerateAttribute:NSFontAttributeName inRange:paragraphRange options:0 + usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { + UIFont *currentFont = (UIFont *)value; + UIFont *newFont = nullptr; + + BOOL isCodeFont = [[currentFont familyName] isEqualToString:[[_input->config monospacedFont] familyName]]; + + if (isCodeFont && !selfDetected) { + newFont = [[[_input->config primaryFont] withFontTraits:currentFont] setSize:currentFont.pointSize]; + } else if (!isCodeFont && selfDetected) { + newFont = [[[_input->config monospacedFont] withFontTraits:currentFont] setSize:currentFont.pointSize]; + } + + if (newFont != nullptr) { + [_input->textView.textStorage addAttribute:NSFontAttributeName value:newFont range:range]; + } + }]; + + [_input->textView.textStorage enumerateAttribute:NSForegroundColorAttributeName inRange:paragraphRange options:0 + usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { + UIColor *newColor = nullptr; + BOOL colorApplied = [(UIColor *)value isEqualToColor:[_input->config codeBlockFgColor]]; + + if(colorApplied && !selfDetected) { + newColor = [_input->config primaryColor]; + } else if(!colorApplied && selfDetected) { + newColor = [_input->config codeBlockFgColor]; + } + + if(newColor != nullptr) { + [_input->textView.textStorage addAttribute:NSForegroundColorAttributeName value:newColor range:range]; + } + } + ]; + } +} + @end diff --git a/ios/utils/ParagraphAttributesUtils.mm b/ios/utils/ParagraphAttributesUtils.mm index 4cf63840..a7ad7ec9 100644 --- a/ios/utils/ParagraphAttributesUtils.mm +++ b/ios/utils/ParagraphAttributesUtils.mm @@ -14,6 +14,7 @@ + (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text i UnorderedListStyle *ulStyle = typedInput->stylesDict[@([UnorderedListStyle getStyleType])]; OrderedListStyle *olStyle = typedInput->stylesDict[@([OrderedListStyle getStyleType])]; BlockQuoteStyle *bqStyle = typedInput->stylesDict[@([BlockQuoteStyle getStyleType])]; + CodeBlockStyle *cbStyle = typedInput->stylesDict[@([CodeBlockStyle getStyleType])]; if(typedInput == nullptr) { return NO; @@ -53,6 +54,11 @@ + (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text i [bqStyle addAttributes:NSMakeRange(range.location, 0)]; return YES; } + if([cbStyle detectStyle:nonNewlineRange]) { + [TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:typedInput withSelection:YES]; + [cbStyle addAttributes:NSMakeRange(range.location, 0)]; + return YES; + } // do the replacement manually [TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:typedInput withSelection:YES]; diff --git a/ios/utils/StyleHeaders.h b/ios/utils/StyleHeaders.h index f7d53352..a4da5118 100644 --- a/ios/utils/StyleHeaders.h +++ b/ios/utils/StyleHeaders.h @@ -76,6 +76,6 @@ @end @interface CodeBlockStyle : NSObject -- (void)manageCodeBlockColor; +- (void)manageCodeBlockFontAndColor; - (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text; @end From 7df0db1f336629934526359203f89ddb8cb033c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Wed, 19 Nov 2025 09:31:56 +0100 Subject: [PATCH 08/15] fix: conflicts in styles --- ios/EnrichedTextInputView.h | 3 ++ ios/EnrichedTextInputView.mm | 14 +++---- ios/styles/BlockQuoteStyle.mm | 2 + ios/styles/BoldStyle.mm | 2 + ios/styles/CodeBlockStyle.mm | 2 + ios/styles/H1Style.mm | 1 + ios/styles/H2Style.mm | 1 + ios/styles/H3Style.mm | 1 + ios/styles/InlineCodeStyle.mm | 2 + ios/styles/ItalicStyle.mm | 2 + ios/styles/LinkStyle.mm | 2 + ios/styles/MentionStyle.mm | 2 + ios/styles/OrderedListStyle.mm | 2 + ios/styles/StrikethroughStyle.mm | 2 + ios/styles/UnderlineStyle.mm | 2 + ios/styles/UnorderedListStyle.mm | 2 + ios/utils/BaseStyleProtocol.h | 1 + ios/utils/ParagraphAttributesUtils.h | 1 + ios/utils/ParagraphAttributesUtils.mm | 53 +++++++++++++++++++++++++++ ios/utils/ZeroWidthSpaceUtils.mm | 17 ++++++--- 20 files changed, 102 insertions(+), 12 deletions(-) diff --git a/ios/EnrichedTextInputView.h b/ios/EnrichedTextInputView.h index ec4670b7..f027e444 100644 --- a/ios/EnrichedTextInputView.h +++ b/ios/EnrichedTextInputView.h @@ -18,6 +18,8 @@ NS_ASSUME_NONNULL_BEGIN @public InputParser *parser; @public NSMutableDictionary *defaultTypingAttributes; @public NSDictionary> *stylesDict; + NSDictionary *> *conflictingStyles; + NSDictionary *> *blockingStyles; @public BOOL blockEmitting; @public BOOL emitHtml; } @@ -26,6 +28,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)emitOnMentionEvent:(NSString *)indicator text:(nullable NSString *)text; - (void)anyTextMayHaveBeenModified; - (BOOL)handleStyleBlocksAndConflicts:(StyleType)type range:(NSRange)range; +- (NSArray *)getPresentStyleTypesFrom:(NSArray *)types range:(NSRange)range; @end NS_ASSUME_NONNULL_END diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index 9675ad35..b5a2a269 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -26,8 +26,6 @@ @implementation EnrichedTextInputView { EnrichedTextInputViewShadowNode::ConcreteState::Shared _state; int _componentViewHeightUpdateCounter; NSMutableSet *_activeStyles; - NSDictionary *> *_conflictingStyles; - NSDictionary *> *_blockingStyles; LinkData *_recentlyActiveLinkData; NSRange _recentlyActiveLinkRange; NSString *_recentlyEmittedString; @@ -98,7 +96,7 @@ - (void)setDefaults { @([CodeBlockStyle getStyleType]): [[CodeBlockStyle alloc] initWithInput:self] }; - _conflictingStyles = @{ + conflictingStyles = @{ @([BoldStyle getStyleType]) : @[], @([ItalicStyle getStyleType]) : @[], @([UnderlineStyle getStyleType]) : @[], @@ -116,7 +114,7 @@ - (void)setDefaults { @([BoldStyle getStyleType]), @([ItalicStyle getStyleType]), @([UnderlineStyle getStyleType]), @([StrikethroughStyle getStyleType]), @([UnorderedListStyle getStyleType]), @([OrderedListStyle getStyleType]), @([BlockQuoteStyle getStyleType]), @([InlineCodeStyle getStyleType]), @([MentionStyle getStyleType]), @([LinkStyle getStyleType])] }; - _blockingStyles = @{ + blockingStyles = @{ @([BoldStyle getStyleType]) : @[@([CodeBlockStyle getStyleType])], @([ItalicStyle getStyleType]) : @[@([CodeBlockStyle getStyleType])], @([UnderlineStyle getStyleType]) : @[@([CodeBlockStyle getStyleType])], @@ -945,13 +943,13 @@ - (void)startMentionWithIndicator:(NSString *)indicator { // returns false when style shouldn't be applied and true when it can be - (BOOL)handleStyleBlocksAndConflicts:(StyleType)type range:(NSRange)range { // handle blocking styles: if any is present we do not apply the toggled style - NSArray *blocking = [self getPresentStyleTypesFrom: _blockingStyles[@(type)] range:range]; + NSArray *blocking = [self getPresentStyleTypesFrom: blockingStyles[@(type)] range:range]; if(blocking.count != 0) { return NO; } // handle conflicting styles: all of their occurences have to be removed - NSArray *conflicting = [self getPresentStyleTypesFrom: _conflictingStyles[@(type)] range:range]; + NSArray *conflicting = [self getPresentStyleTypesFrom: conflictingStyles[@(type)] range:range]; if(conflicting.count != 0) { for(NSNumber *style in conflicting) { id styleClass = stylesDict[style]; @@ -1212,7 +1210,9 @@ - (bool)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range r [h2Style handleNewlinesInRange:range replacementText:text] || [h3Style handleNewlinesInRange:range replacementText:text] || [ZeroWidthSpaceUtils handleBackspaceInRange:range replacementText:text input:self] || - [ParagraphAttributesUtils handleBackspaceInRange:range replacementText:text input:self] + [ParagraphAttributesUtils handleBackspaceInRange:range replacementText:text input:self] || + // this callback HAS TO be always evaluated last + [ParagraphAttributesUtils handleNewlineBackspaceInRange:range replacementText:text input:self] ) { [self anyTextMayHaveBeenModified]; return NO; diff --git a/ios/styles/BlockQuoteStyle.mm b/ios/styles/BlockQuoteStyle.mm index 4c04cbb1..250ec343 100644 --- a/ios/styles/BlockQuoteStyle.mm +++ b/ios/styles/BlockQuoteStyle.mm @@ -11,6 +11,8 @@ @implementation BlockQuoteStyle { + (StyleType)getStyleType { return BlockQuote; } ++ (BOOL)isParagraphStyle { return YES; } + - (instancetype)initWithInput:(id)input { self = [super init]; _input = (EnrichedTextInputView *)input; diff --git a/ios/styles/BoldStyle.mm b/ios/styles/BoldStyle.mm index 2dd50739..78b80b4a 100644 --- a/ios/styles/BoldStyle.mm +++ b/ios/styles/BoldStyle.mm @@ -9,6 +9,8 @@ @implementation BoldStyle { + (StyleType)getStyleType { return Bold; } ++ (BOOL)isParagraphStyle { return NO; } + - (instancetype)initWithInput:(id)input { self = [super init]; _input = (EnrichedTextInputView *)input; diff --git a/ios/styles/CodeBlockStyle.mm b/ios/styles/CodeBlockStyle.mm index 798afb96..a7f3e27a 100644 --- a/ios/styles/CodeBlockStyle.mm +++ b/ios/styles/CodeBlockStyle.mm @@ -12,6 +12,8 @@ @implementation CodeBlockStyle { + (StyleType)getStyleType { return CodeBlock; } ++ (BOOL)isParagraphStyle { return YES; } + - (instancetype)initWithInput:(id)input { self = [super init]; _input = (EnrichedTextInputView *)input; diff --git a/ios/styles/H1Style.mm b/ios/styles/H1Style.mm index 9a8c4ded..3721254b 100644 --- a/ios/styles/H1Style.mm +++ b/ios/styles/H1Style.mm @@ -3,6 +3,7 @@ @implementation H1Style + (StyleType)getStyleType { return H1; } ++ (BOOL)isParagraphStyle { return NO; } - (CGFloat)getHeadingFontSize { return [((EnrichedTextInputView *)input)->config h1FontSize]; } - (BOOL)isHeadingBold { return [((EnrichedTextInputView *)input)->config h1Bold]; diff --git a/ios/styles/H2Style.mm b/ios/styles/H2Style.mm index 1913c2f3..a80be397 100644 --- a/ios/styles/H2Style.mm +++ b/ios/styles/H2Style.mm @@ -3,6 +3,7 @@ @implementation H2Style + (StyleType)getStyleType { return H2; } ++ (BOOL)isParagraphStyle { return NO; } - (CGFloat)getHeadingFontSize { return [((EnrichedTextInputView *)input)->config h2FontSize]; } - (BOOL)isHeadingBold { return [((EnrichedTextInputView *)input)->config h2Bold]; diff --git a/ios/styles/H3Style.mm b/ios/styles/H3Style.mm index f7b55bc7..84791819 100644 --- a/ios/styles/H3Style.mm +++ b/ios/styles/H3Style.mm @@ -3,6 +3,7 @@ @implementation H3Style + (StyleType)getStyleType { return H3; } ++ (BOOL)isParagraphStyle { return NO; } - (CGFloat)getHeadingFontSize { return [((EnrichedTextInputView *)input)->config h3FontSize]; } - (BOOL)isHeadingBold { return [((EnrichedTextInputView *)input)->config h3Bold]; diff --git a/ios/styles/InlineCodeStyle.mm b/ios/styles/InlineCodeStyle.mm index 94d8ad00..cb6a0b59 100644 --- a/ios/styles/InlineCodeStyle.mm +++ b/ios/styles/InlineCodeStyle.mm @@ -11,6 +11,8 @@ @implementation InlineCodeStyle { + (StyleType)getStyleType { return InlineCode; } ++ (BOOL)isParagraphStyle { return NO; } + - (instancetype)initWithInput:(id)input { self = [super init]; _input = (EnrichedTextInputView *)input; diff --git a/ios/styles/ItalicStyle.mm b/ios/styles/ItalicStyle.mm index 6ec4debc..0640689d 100644 --- a/ios/styles/ItalicStyle.mm +++ b/ios/styles/ItalicStyle.mm @@ -9,6 +9,8 @@ @implementation ItalicStyle { + (StyleType)getStyleType { return Italic; } ++ (BOOL)isParagraphStyle { return NO; } + - (instancetype)initWithInput:(id)input { self = [super init]; _input = (EnrichedTextInputView *)input; diff --git a/ios/styles/LinkStyle.mm b/ios/styles/LinkStyle.mm index 04bbbcb8..2ddf28d1 100644 --- a/ios/styles/LinkStyle.mm +++ b/ios/styles/LinkStyle.mm @@ -15,6 +15,8 @@ @implementation LinkStyle { + (StyleType)getStyleType { return Link; } ++ (BOOL)isParagraphStyle { return NO; } + - (instancetype)initWithInput:(id)input { self = [super init]; _input = (EnrichedTextInputView *)input; diff --git a/ios/styles/MentionStyle.mm b/ios/styles/MentionStyle.mm index f0ec496a..d2fb904a 100644 --- a/ios/styles/MentionStyle.mm +++ b/ios/styles/MentionStyle.mm @@ -18,6 +18,8 @@ @implementation MentionStyle { + (StyleType)getStyleType { return Mention; } ++ (BOOL)isParagraphStyle { return NO; } + - (instancetype)initWithInput:(id)input { self = [super init]; _input = (EnrichedTextInputView *)input; diff --git a/ios/styles/OrderedListStyle.mm b/ios/styles/OrderedListStyle.mm index 472118df..9cb06424 100644 --- a/ios/styles/OrderedListStyle.mm +++ b/ios/styles/OrderedListStyle.mm @@ -11,6 +11,8 @@ @implementation OrderedListStyle { + (StyleType)getStyleType { return OrderedList; } ++ (BOOL)isParagraphStyle { return YES; } + - (CGFloat)getHeadIndent { // lists are drawn manually // margin before marker + gap between marker and paragraph diff --git a/ios/styles/StrikethroughStyle.mm b/ios/styles/StrikethroughStyle.mm index 82932384..8ff74fd2 100644 --- a/ios/styles/StrikethroughStyle.mm +++ b/ios/styles/StrikethroughStyle.mm @@ -8,6 +8,8 @@ @implementation StrikethroughStyle { + (StyleType)getStyleType { return Strikethrough; } ++ (BOOL)isParagraphStyle { return NO; } + - (instancetype)initWithInput:(id)input { self = [super init]; _input = (EnrichedTextInputView *)input; diff --git a/ios/styles/UnderlineStyle.mm b/ios/styles/UnderlineStyle.mm index f36aa089..933ff897 100644 --- a/ios/styles/UnderlineStyle.mm +++ b/ios/styles/UnderlineStyle.mm @@ -8,6 +8,8 @@ @implementation UnderlineStyle { + (StyleType)getStyleType { return Underline; } ++ (BOOL)isParagraphStyle { return NO; } + - (instancetype)initWithInput:(id)input { self = [super init]; _input = (EnrichedTextInputView *)input; diff --git a/ios/styles/UnorderedListStyle.mm b/ios/styles/UnorderedListStyle.mm index e161fc2d..3c6f7237 100644 --- a/ios/styles/UnorderedListStyle.mm +++ b/ios/styles/UnorderedListStyle.mm @@ -11,6 +11,8 @@ @implementation UnorderedListStyle { + (StyleType)getStyleType { return UnorderedList; } ++ (BOOL)isParagraphStyle { return YES; } + - (CGFloat)getHeadIndent { // lists are drawn manually // margin before bullet + gap between bullet and paragraph diff --git a/ios/utils/BaseStyleProtocol.h b/ios/utils/BaseStyleProtocol.h index 82e59886..1685773b 100644 --- a/ios/utils/BaseStyleProtocol.h +++ b/ios/utils/BaseStyleProtocol.h @@ -4,6 +4,7 @@ @protocol BaseStyleProtocol + (StyleType)getStyleType; ++ (BOOL)isParagraphStyle; - (instancetype _Nonnull)initWithInput:(id _Nonnull)input; - (void)applyStyle:(NSRange)range; - (void)addAttributes:(NSRange)range; diff --git a/ios/utils/ParagraphAttributesUtils.h b/ios/utils/ParagraphAttributesUtils.h index 0a1fd3a3..f9f07d8d 100644 --- a/ios/utils/ParagraphAttributesUtils.h +++ b/ios/utils/ParagraphAttributesUtils.h @@ -3,4 +3,5 @@ @interface ParagraphAttributesUtils : NSObject + (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text input:(id)input; ++ (BOOL)handleNewlineBackspaceInRange:(NSRange)range replacementText:(NSString *)text input:(id)input; @end diff --git a/ios/utils/ParagraphAttributesUtils.mm b/ios/utils/ParagraphAttributesUtils.mm index a7ad7ec9..dcc519c4 100644 --- a/ios/utils/ParagraphAttributesUtils.mm +++ b/ios/utils/ParagraphAttributesUtils.mm @@ -70,4 +70,57 @@ + (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text i return NO; } ++ (BOOL)handleNewlineBackspaceInRange:(NSRange)range replacementText:(NSString *)text input:(id)input { + EnrichedTextInputView *typedInput = (EnrichedTextInputView *)input; + if(typedInput == nullptr) { + return NO; + } + + if(text.length == 0 && range.length == 1 && + [[NSCharacterSet newlineCharacterSet] characterIsMember:[typedInput->textView.textStorage.string characterAtIndex:range.location]]) { + NSRange leftRange = [typedInput->textView.textStorage.string paragraphRangeForRange:range]; + + id leftParagraphStyle = nullptr; + for (NSNumber *key in typedInput->stylesDict) { + id style = typedInput->stylesDict[key]; + if([[style class] isParagraphStyle] && [style detectStyle:leftRange]) { + leftParagraphStyle = style; + } + } + + if(leftParagraphStyle == nullptr) { + return NO; + } + + // index out of bounds + if(range.location + 1 >= typedInput->textView.textStorage.string.length) { + return NO; + } + + NSRange rightRange = [typedInput->textView.textStorage.string paragraphRangeForRange:NSMakeRange(range.location + 1, 1)]; + + StyleType type = [[leftParagraphStyle class] getStyleType]; + + NSArray *conflictingStyles = [typedInput getPresentStyleTypesFrom:typedInput->conflictingStyles[@(type)] range:rightRange]; + NSArray *blockingStyles = [typedInput getPresentStyleTypesFrom:typedInput->blockingStyles[@(type)] range:rightRange]; + NSArray *allToBeRemoved = [conflictingStyles arrayByAddingObjectsFromArray:blockingStyles]; + + for(NSNumber *style in allToBeRemoved) { + id styleClass = typedInput->stylesDict[style]; + + // for ranges, we need to remove each occurence + NSArray *allOccurences = [styleClass findAllOccurences:rightRange]; + + for(StylePair* pair in allOccurences) { + [styleClass removeAttributes: [pair.rangeValue rangeValue]]; + } + } + + [TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:typedInput withSelection:YES]; + return YES; + } + + return NO; +} + @end diff --git a/ios/utils/ZeroWidthSpaceUtils.mm b/ios/utils/ZeroWidthSpaceUtils.mm index c02f5037..1d0ecdcc 100644 --- a/ios/utils/ZeroWidthSpaceUtils.mm +++ b/ios/utils/ZeroWidthSpaceUtils.mm @@ -148,8 +148,6 @@ + (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text i styleRemovalRange = NSMakeRange(paragraphRange.location, 1); } - [TextInsertionUtils replaceText:@"" at:removalRange additionalAttributes:nullptr input:typedInput withSelection:YES]; - // and then remove associated styling UnorderedListStyle *ulStyle = typedInput->stylesDict[@([UnorderedListStyle getStyleType])]; @@ -157,19 +155,28 @@ + (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text i BlockQuoteStyle *bqStyle = typedInput->stylesDict[@([BlockQuoteStyle getStyleType])]; CodeBlockStyle *cbStyle = typedInput->stylesDict[@([CodeBlockStyle getStyleType])]; + if([cbStyle detectStyle:removalRange]) { + // code blocks are being handled differently; we want to remove previous newline if there is a one + if(range.location > 0) { + removalRange = NSMakeRange(removalRange.location - 1, removalRange.length + 1); + } + [TextInsertionUtils replaceText:@"" at:removalRange additionalAttributes:nullptr input:typedInput withSelection:YES]; + return YES; + } + + [TextInsertionUtils replaceText:@"" at:removalRange additionalAttributes:nullptr input:typedInput withSelection:YES]; + if ([ulStyle detectStyle:styleRemovalRange]) { [ulStyle removeAttributes:styleRemovalRange]; } else if ([olStyle detectStyle:styleRemovalRange]) { [olStyle removeAttributes:styleRemovalRange]; } else if ([bqStyle detectStyle:styleRemovalRange]) { [bqStyle removeAttributes:styleRemovalRange]; - }else if ([cbStyle detectStyle:styleRemovalRange]) { - [cbStyle removeAttributes:styleRemovalRange]; } return YES; } return NO; } -@end +@end From eafe33b1a219592d4753f367dfc3e833ab437d53 Mon Sep 17 00:00:00 2001 From: szydlovsky <9szydlowski9@gmail.com> Date: Wed, 19 Nov 2025 12:59:50 +0100 Subject: [PATCH 09/15] fix: mentions uneditable in codeblock --- ios/styles/MentionStyle.mm | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/ios/styles/MentionStyle.mm b/ios/styles/MentionStyle.mm index d2fb904a..906e3ebf 100644 --- a/ios/styles/MentionStyle.mm +++ b/ios/styles/MentionStyle.mm @@ -329,16 +329,22 @@ - (void)manageMentionEditing { return; } - // get conflicting style classes - LinkStyle* linkStyle = [_input->stylesDict objectForKey:@([LinkStyle getStyleType])]; - InlineCodeStyle* inlineCodeStyle = [_input->stylesDict objectForKey:@([InlineCodeStyle getStyleType])]; - if(linkStyle == nullptr || inlineCodeStyle == nullptr) { - [self removeActiveMentionRange]; - return; + // get style classes that the mention shouldn't be recognized in + NSArray *conflicts = _input->conflictingStyles[@([MentionStyle getStyleType])]; + NSArray *blocks = _input->blockingStyles[@([MentionStyle getStyleType])]; + NSArray *allConflicts = [conflicts arrayByAddingObjectsFromArray:blocks]; + BOOL conflictingStyle = NO; + + for(NSNumber *styleType in allConflicts) { + id styleClass = _input->stylesDict[styleType]; + if(styleClass != nullptr && [styleClass anyOccurence:wordRange]) { + conflictingStyle = YES; + break; + } } - // if there is any sign of conflicting style classes, stop editing a mention - if([linkStyle anyOccurence:wordRange] || [inlineCodeStyle anyOccurence:wordRange]) { + // if any of the conflicting styles were present, don't edit the mention + if(conflictingStyle) { [self removeActiveMentionRange]; return; } From f3ce536bad6d088d75f529dc8195f930af5525ad Mon Sep 17 00:00:00 2001 From: szydlovsky <9szydlowski9@gmail.com> Date: Wed, 19 Nov 2025 15:53:56 +0100 Subject: [PATCH 10/15] fix: update mention editing when toggling styles as well --- ios/EnrichedTextInputView.mm | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index b5a2a269..9b5989ac 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -533,9 +533,8 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & emitHtml = newViewProps.isOnChangeHtmlSet; [super updateProps:props oldProps:oldProps]; - // mandatory text and height checks + // run the changes callback [self anyTextMayHaveBeenModified]; - [self tryUpdatingHeight]; // autofocus - needs to be done at the very end if(isFirstMount && newViewProps.autoFocus) { @@ -1073,6 +1072,9 @@ - (void)anyTextMayHaveBeenModified { [h3Style handleImproperHeadings]; } + // additionally, manage selection based changes + [self manageSelectionBasedChanges]; + // placholder management if(!_placeholderLabel.hidden && textView.textStorage.string.length > 0) { [self setPlaceholderLabelShown:NO]; From 7fd08fdf539a8a912b86fe9fced6e824a9f4eae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Wed, 19 Nov 2025 15:54:58 +0100 Subject: [PATCH 11/15] fix: applying codeblock styles --- ios/styles/BlockQuoteStyle.mm | 29 +++------------------ ios/styles/CodeBlockStyle.mm | 48 ++++++++++++++++++++--------------- ios/styles/LinkStyle.mm | 4 +-- ios/utils/OccurenceUtils.h | 2 ++ ios/utils/OccurenceUtils.mm | 45 ++++++++++++++++++++++++++++++++ 5 files changed, 79 insertions(+), 49 deletions(-) diff --git a/ios/styles/BlockQuoteStyle.mm b/ios/styles/BlockQuoteStyle.mm index 250ec343..48f33036 100644 --- a/ios/styles/BlockQuoteStyle.mm +++ b/ios/styles/BlockQuoteStyle.mm @@ -7,6 +7,7 @@ @implementation BlockQuoteStyle { EnrichedTextInputView *_input; + NSArray *_stylesToExclude; } + (StyleType)getStyleType { return BlockQuote; } @@ -16,6 +17,7 @@ + (BOOL)isParagraphStyle { return YES; } - (instancetype)initWithInput:(id)input { self = [super init]; _input = (EnrichedTextInputView *)input; + _stylesToExclude = @[ @(InlineCode), @(Mention), @(Link) ]; return self; } @@ -177,31 +179,6 @@ - (BOOL)anyOccurence:(NSRange)range { ]; } -// gets ranges that aren't link, mention or inline code -- (NSArray *)getProperColorRangesIn:(NSRange)range { - LinkStyle *linkStyle = _input->stylesDict[@([LinkStyle getStyleType])]; - MentionStyle *mentionStyle = _input->stylesDict[@([MentionStyle getStyleType])]; - InlineCodeStyle *codeStyle = _input->stylesDict[@([InlineCodeStyle getStyleType])]; - - NSMutableArray *newRanges = [[NSMutableArray alloc] init]; - int lastRangeLocation = range.location; - - for(int i = range.location; i < range.location + range.length; i++) { - NSRange currentRange = NSMakeRange(i, 1); - if([linkStyle detectStyle:currentRange] || [mentionStyle detectStyle:currentRange] || [codeStyle detectStyle:currentRange]) { - if(i - lastRangeLocation > 0) { - [newRanges addObject:[NSValue valueWithRange:NSMakeRange(lastRangeLocation, i - lastRangeLocation)]]; - } - lastRangeLocation = i+1; - } - } - if(lastRangeLocation < range.location + range.length) { - [newRanges addObject:[NSValue valueWithRange:NSMakeRange(lastRangeLocation, range.location + range.length - lastRangeLocation)]]; - } - - return newRanges; -} - // general checkup correcting blockquote color // since links, mentions and inline code affects coloring, the checkup gets done only outside of them - (void)manageBlockquoteColor { @@ -214,7 +191,7 @@ - (void)manageBlockquoteColor { NSArray *paragraphs = [ParagraphsUtils getSeparateParagraphsRangesIn:_input->textView range:wholeRange]; for(NSValue *pValue in paragraphs) { NSRange paragraphRange = [pValue rangeValue]; - NSArray *properRanges = [self getProperColorRangesIn:paragraphRange]; + NSArray *properRanges = [OccurenceUtils getRangesWithout:_stylesToExclude withInput:_input inRange:paragraphRange]; for(NSValue *value in properRanges) { NSRange currRange = [value rangeValue]; diff --git a/ios/styles/CodeBlockStyle.mm b/ios/styles/CodeBlockStyle.mm index a7f3e27a..9f1de33e 100644 --- a/ios/styles/CodeBlockStyle.mm +++ b/ios/styles/CodeBlockStyle.mm @@ -8,6 +8,7 @@ @implementation CodeBlockStyle { EnrichedTextInputView *_input; + NSArray *_stylesToExclude; } + (StyleType)getStyleType { return CodeBlock; } @@ -17,6 +18,7 @@ + (BOOL)isParagraphStyle { return YES; } - (instancetype)initWithInput:(id)input { self = [super init]; _input = (EnrichedTextInputView *)input; + _stylesToExclude = @[ @(InlineCode), @(Mention), @(Link) ]; return self; } @@ -180,28 +182,32 @@ - (void)manageCodeBlockFontAndColor { for(NSValue *pValue in paragraphs) { NSRange paragraphRange = [pValue rangeValue]; - BOOL selfDetected = [self detectStyle:paragraphRange]; + NSArray *properRanges = [OccurenceUtils getRangesWithout:_stylesToExclude withInput:_input inRange:paragraphRange]; - [_input->textView.textStorage enumerateAttribute:NSFontAttributeName inRange:paragraphRange options:0 - usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { - UIFont *currentFont = (UIFont *)value; - UIFont *newFont = nullptr; + for(NSValue *value in properRanges) { + NSRange currRange = [value rangeValue]; + BOOL selfDetected = [self detectStyle:currRange]; - BOOL isCodeFont = [[currentFont familyName] isEqualToString:[[_input->config monospacedFont] familyName]]; - - if (isCodeFont && !selfDetected) { - newFont = [[[_input->config primaryFont] withFontTraits:currentFont] setSize:currentFont.pointSize]; - } else if (!isCodeFont && selfDetected) { - newFont = [[[_input->config monospacedFont] withFontTraits:currentFont] setSize:currentFont.pointSize]; - } + [_input->textView.textStorage enumerateAttribute:NSFontAttributeName inRange:currRange options:0 + usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { + UIFont *currentFont = (UIFont *)value; + UIFont *newFont = nullptr; + + BOOL isCodeFont = [[currentFont familyName] isEqualToString:[[_input->config monospacedFont] familyName]]; + + if (isCodeFont && !selfDetected) { + newFont = [[[_input->config primaryFont] withFontTraits:currentFont] setSize:currentFont.pointSize]; + } else if (!isCodeFont && selfDetected) { + newFont = [[[_input->config monospacedFont] withFontTraits:currentFont] setSize:currentFont.pointSize]; + } + + if (newFont != nullptr) { + [_input->textView.textStorage addAttribute:NSFontAttributeName value:newFont range:range]; + } + }]; - if (newFont != nullptr) { - [_input->textView.textStorage addAttribute:NSFontAttributeName value:newFont range:range]; - } - }]; - - [_input->textView.textStorage enumerateAttribute:NSForegroundColorAttributeName inRange:paragraphRange options:0 - usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { + [_input->textView.textStorage enumerateAttribute:NSForegroundColorAttributeName inRange:currRange options:0 + usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) { UIColor *newColor = nullptr; BOOL colorApplied = [(UIColor *)value isEqualToColor:[_input->config codeBlockFgColor]]; @@ -214,8 +220,8 @@ - (void)manageCodeBlockFontAndColor { if(newColor != nullptr) { [_input->textView.textStorage addAttribute:NSForegroundColorAttributeName value:newColor range:range]; } - } - ]; + }]; + } } } diff --git a/ios/styles/LinkStyle.mm b/ios/styles/LinkStyle.mm index 2ddf28d1..53c435dd 100644 --- a/ios/styles/LinkStyle.mm +++ b/ios/styles/LinkStyle.mm @@ -226,7 +226,7 @@ - (NSRange)getFullLinkRangeAt:(NSUInteger)location { } } - [_input->textView.textStorage + NSString *manualUrl = [_input->textView.textStorage attribute:ManualLinkAttributeName atIndex:searchLocation longestEffectiveRange: &manualLinkRange @@ -239,7 +239,7 @@ - (NSRange)getFullLinkRangeAt:(NSUInteger)location { inRange:inputRange ]; - return manualLinkRange.length == 0 ? automaticLinkRange : manualLinkRange; + return manualUrl == nil ? automaticLinkRange : manualLinkRange; } - (void)manageLinkTypingAttributes { diff --git a/ios/utils/OccurenceUtils.h b/ios/utils/OccurenceUtils.h index 885cb212..e12ca707 100644 --- a/ios/utils/OccurenceUtils.h +++ b/ios/utils/OccurenceUtils.h @@ -40,4 +40,6 @@ 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 796282eb..70480824 100644 --- a/ios/utils/OccurenceUtils.mm +++ b/ios/utils/OccurenceUtils.mm @@ -152,4 +152,49 @@ + (BOOL)anyMultiple 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; + } + } + + if (forbiddenStyleFound) { + if (i > lastRangeLocation) { + NSRange cleanRange = NSMakeRange(lastRangeLocation, i - lastRangeLocation); + [newRanges addObject:[NSValue valueWithRange:cleanRange]]; + } + lastRangeLocation = i + 1; + } + } + + if (lastRangeLocation < endLocation) { + NSRange remainingRange = NSMakeRange(lastRangeLocation, endLocation - lastRangeLocation); + [newRanges addObject:[NSValue valueWithRange:remainingRange]]; + } + + return newRanges; +} + @end From e4b32a6f4f24f1665b5df2c13125f4407eca6de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Wed, 19 Nov 2025 16:16:50 +0100 Subject: [PATCH 12/15] fix: code review fixes --- ios/EnrichedTextInputView.mm | 5 ++- ios/styles/CodeBlockStyle.mm | 2 +- ios/styles/H1Style.mm | 2 +- ios/styles/H2Style.mm | 2 +- ios/styles/H3Style.mm | 2 +- ios/styles/LinkStyle.mm | 2 +- ios/utils/ParagraphAttributesUtils.mm | 44 +++++++++++++++------------ ios/utils/ZeroWidthSpaceUtils.mm | 2 +- 8 files changed, 34 insertions(+), 27 deletions(-) diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index 2e735b29..6feffcc5 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -1223,7 +1223,10 @@ - (bool)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range r [h3Style handleNewlinesInRange:range replacementText:text] || [ZeroWidthSpaceUtils handleBackspaceInRange:range replacementText:text input:self] || [ParagraphAttributesUtils handleBackspaceInRange:range replacementText:text input:self] || - // this callback HAS TO be always evaluated last + // CRITICAL: This callback HAS TO be always evaluated last. + // + // This function is the "Generic Fallback": if no specific style claims the backspace action + // to change its state, only then do we proceed to physically delete the newline and merge paragraphs. [ParagraphAttributesUtils handleNewlineBackspaceInRange:range replacementText:text input:self] ) { [self anyTextMayHaveBeenModified]; diff --git a/ios/styles/CodeBlockStyle.mm b/ios/styles/CodeBlockStyle.mm index 9f1de33e..cf1969d7 100644 --- a/ios/styles/CodeBlockStyle.mm +++ b/ios/styles/CodeBlockStyle.mm @@ -137,7 +137,7 @@ - (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text { - (BOOL)styleCondition:(id _Nullable)value :(NSRange)range { NSParagraphStyle *paragraph = (NSParagraphStyle *)value; - return paragraph != nullptr && paragraph.textLists.count == 1 && [paragraph.textLists.firstObject.markerFormat isEqual:@"codeblock"]; + return paragraph != nullptr && paragraph.textLists.count == 1 && [paragraph.textLists.firstObject.markerFormat isEqualToString:@"codeblock"]; } - (BOOL)detectStyle:(NSRange)range { diff --git a/ios/styles/H1Style.mm b/ios/styles/H1Style.mm index 3721254b..0b5add38 100644 --- a/ios/styles/H1Style.mm +++ b/ios/styles/H1Style.mm @@ -3,7 +3,7 @@ @implementation H1Style + (StyleType)getStyleType { return H1; } -+ (BOOL)isParagraphStyle { return NO; } ++ (BOOL)isParagraphStyle { return YES; } - (CGFloat)getHeadingFontSize { return [((EnrichedTextInputView *)input)->config h1FontSize]; } - (BOOL)isHeadingBold { return [((EnrichedTextInputView *)input)->config h1Bold]; diff --git a/ios/styles/H2Style.mm b/ios/styles/H2Style.mm index a80be397..158634b4 100644 --- a/ios/styles/H2Style.mm +++ b/ios/styles/H2Style.mm @@ -3,7 +3,7 @@ @implementation H2Style + (StyleType)getStyleType { return H2; } -+ (BOOL)isParagraphStyle { return NO; } ++ (BOOL)isParagraphStyle { return YES; } - (CGFloat)getHeadingFontSize { return [((EnrichedTextInputView *)input)->config h2FontSize]; } - (BOOL)isHeadingBold { return [((EnrichedTextInputView *)input)->config h2Bold]; diff --git a/ios/styles/H3Style.mm b/ios/styles/H3Style.mm index 84791819..49ffefb1 100644 --- a/ios/styles/H3Style.mm +++ b/ios/styles/H3Style.mm @@ -3,7 +3,7 @@ @implementation H3Style + (StyleType)getStyleType { return H3; } -+ (BOOL)isParagraphStyle { return NO; } ++ (BOOL)isParagraphStyle { return YES; } - (CGFloat)getHeadingFontSize { return [((EnrichedTextInputView *)input)->config h3FontSize]; } - (BOOL)isHeadingBold { return [((EnrichedTextInputView *)input)->config h3Bold]; diff --git a/ios/styles/LinkStyle.mm b/ios/styles/LinkStyle.mm index 53c435dd..b7bf01db 100644 --- a/ios/styles/LinkStyle.mm +++ b/ios/styles/LinkStyle.mm @@ -297,7 +297,7 @@ - (void)handleAutomaticLinks:(NSString *)word inRange:(NSRange)wordRange { return; } - // we don't recognize links among codeblock + // we don't recognize links in codeblocks if ([codeBlockStyle anyOccurence:wordRange]) { return; } diff --git a/ios/utils/ParagraphAttributesUtils.mm b/ios/utils/ParagraphAttributesUtils.mm index dcc519c4..c2754e45 100644 --- a/ios/utils/ParagraphAttributesUtils.mm +++ b/ios/utils/ParagraphAttributesUtils.mm @@ -37,27 +37,14 @@ + (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text i // if the backspace removes the whole content of a paragraph (possibly more but has to start where the paragraph starts), we remove the typing attributes if(range.location == nonNewlineRange.location && range.length >= nonNewlineRange.length) { - // for lists and quotes we want to remove the characters but keep attribtues so that a zero width space appears here + // for lists, quotes and codeblocks we want to remove the characters but keep the attributes so that a zero width space appears here // so we do the removing manually and reapply attributes - if([ulStyle detectStyle:nonNewlineRange]) { - [TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:typedInput withSelection:YES]; - [ulStyle addAttributes:NSMakeRange(range.location, 0)]; - return YES; - } - if([olStyle detectStyle:nonNewlineRange]) { - [TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:typedInput withSelection:YES]; - [olStyle addAttributes:NSMakeRange(range.location, 0)]; - return YES; - } - if([bqStyle detectStyle:nonNewlineRange]) { - [TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:typedInput withSelection:YES]; - [bqStyle addAttributes:NSMakeRange(range.location, 0)]; - return YES; - } - if([cbStyle detectStyle:nonNewlineRange]) { - [TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:typedInput withSelection:YES]; - [cbStyle addAttributes:NSMakeRange(range.location, 0)]; - return YES; + NSArray *handledStyles = @[ulStyle, olStyle, bqStyle, cbStyle]; + for(id style in handledStyles) { + if([style detectStyle:nonNewlineRange]) { + [TextInsertionUtils replaceText:text at:range additionalAttributes:nullptr input:typedInput withSelection:YES]; + return YES; + } } // do the replacement manually @@ -70,6 +57,23 @@ + (BOOL)handleBackspaceInRange:(NSRange)range replacementText:(NSString *)text i return NO; } +/** + * Handles the specific case of backspacing a newline character, which results in merging two paragraphs. + * + * THE PROBLEM: + * When merging a bottom paragraph (Source) into a top paragraph (Destination), the bottom paragraph + * normally brings all its attributes with it. If the top paragraph is a restrictive style (like a CodeBlock), + * and the bottom paragraph contains a conflicting style (like an H1 Header), a standard merge would + * create an invalid state (e.g., a CodeBlock that is also a Header). + * + * THE SOLUTION: + * 1. Identifies the dominant style of the paragraph ABOVE the deleted newline (`leftParagraphStyle`). + * 2. Checks the paragraph BELOW the newline (`rightRange`) for any styles that conflict with or are blocked by the top style. + * 3. Explicitly removes those forbidden styles from the bottom paragraph *before* the merge occurs. + * 4. Performs the merge (deletes the newline). + * + * @return YES if the newline backspace was handled and sanitized; NO otherwise. + */ + (BOOL)handleNewlineBackspaceInRange:(NSRange)range replacementText:(NSString *)text input:(id)input { EnrichedTextInputView *typedInput = (EnrichedTextInputView *)input; if(typedInput == nullptr) { diff --git a/ios/utils/ZeroWidthSpaceUtils.mm b/ios/utils/ZeroWidthSpaceUtils.mm index 1d0ecdcc..e467289f 100644 --- a/ios/utils/ZeroWidthSpaceUtils.mm +++ b/ios/utils/ZeroWidthSpaceUtils.mm @@ -44,7 +44,7 @@ + (void)removeSpacesIfNeededinInput:(EnrichedTextInputView *)input { BlockQuoteStyle *bqStyle = input->stylesDict[@([BlockQuoteStyle getStyleType])]; CodeBlockStyle *cbStyle = input->stylesDict[@([CodeBlockStyle getStyleType])]; - // zero width spaces with no lists/blockquote styles on them get removed + // zero width spaces with no lists/blockquotes/codeblocks on them get removed if(![ulStyle detectStyle:characterRange] && ![olStyle detectStyle:characterRange] && ![bqStyle detectStyle:characterRange] && ![cbStyle detectStyle:characterRange]) { [indexesToBeRemoved addObject:@(characterRange.location)]; } From 807dd6c0ebd36ffbfdaa3a24c87d01f5ddb9fe57 Mon Sep 17 00:00:00 2001 From: szydlovsky <9szydlowski9@gmail.com> Date: Wed, 19 Nov 2025 20:13:02 +0100 Subject: [PATCH 13/15] fix: use only mention editing callback on style changes --- ios/EnrichedTextInputView.mm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ios/EnrichedTextInputView.mm b/ios/EnrichedTextInputView.mm index 6feffcc5..5d5639af 100644 --- a/ios/EnrichedTextInputView.mm +++ b/ios/EnrichedTextInputView.mm @@ -1081,8 +1081,11 @@ - (void)anyTextMayHaveBeenModified { [h3Style handleImproperHeadings]; } - // additionally, manage selection based changes - [self manageSelectionBasedChanges]; + // manage mention editing also here + MentionStyle *mentionStyle = stylesDict[@([MentionStyle getStyleType])]; + if(mentionStyle != nullptr) { + [mentionStyle manageMentionEditing]; + } // placholder management if(!_placeholderLabel.hidden && textView.textStorage.string.length > 0) { From 046268a0b18f89097615000284c8d335ce8c6fdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= Date: Thu, 20 Nov 2025 08:14:59 +0100 Subject: [PATCH 14/15] fix: code formatting --- ios/utils/OccurenceUtils.h | 6 ++++-- ios/utils/OccurenceUtils.mm | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ios/utils/OccurenceUtils.h b/ios/utils/OccurenceUtils.h index e12ca707..3c320a2d 100644 --- a/ios/utils/OccurenceUtils.h +++ b/ios/utils/OccurenceUtils.h @@ -40,6 +40,8 @@ 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 70480824..e575f15c 100644 --- a/ios/utils/OccurenceUtils.mm +++ b/ios/utils/OccurenceUtils.mm @@ -152,8 +152,10 @@ + (BOOL)anyMultiple return occurences; } -+ (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 { NSMutableArray *activeStyleObjects = [[NSMutableArray alloc] init]; for(NSNumber *type in types) { From 42dcf41cf881d2290167a6e5bebdc2ba245d7b14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20=C5=BB=C3=B3=C5=82kiewski?= <74975508+kacperzolkiewski@users.noreply.github.com> Date: Thu, 20 Nov 2025 08:21:22 +0100 Subject: [PATCH 15/15] Update ios/styles/LinkStyle.mm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Mikołaj Szydłowski <9szydlowski9@gmail.com> --- ios/styles/LinkStyle.mm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ios/styles/LinkStyle.mm b/ios/styles/LinkStyle.mm index b7bf01db..4136cb92 100644 --- a/ios/styles/LinkStyle.mm +++ b/ios/styles/LinkStyle.mm @@ -239,7 +239,10 @@ - (NSRange)getFullLinkRangeAt:(NSUInteger)location { inRange:inputRange ]; - return manualUrl == nil ? automaticLinkRange : manualLinkRange; + return + manualLink == nullptr + ? automaticLink == nullptr ? NSMakeRange(0,0) : automaticLinkRange + : manualLinkRange; } - (void)manageLinkTypingAttributes {