Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 41 additions & 10 deletions ios/ReactNativeRichTextEditorView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ - (void)setDefaults {
@([StrikethroughStyle getStyleType]): [[StrikethroughStyle alloc] initWithEditor:self],
@([InlineCodeStyle getStyleType]): [[InlineCodeStyle alloc] initWithEditor:self],
@([LinkStyle getStyleType]): [[LinkStyle alloc] initWithEditor:self],
@([MentionStyle getStyleType]): [[MentionStyle alloc] initWithEditor:self]
@([MentionStyle getStyleType]): [[MentionStyle alloc] initWithEditor:self],
@([H1Style getStyleType]): [[H1Style alloc] initWithEditor:self],
@([H2Style getStyleType]): [[H2Style alloc] initWithEditor:self],
@([H3Style getStyleType]): [[H3Style alloc] initWithEditor:self]
};

_conflictingStyles = @{
Expand All @@ -96,7 +99,10 @@ - (void)setDefaults {
@([StrikethroughStyle getStyleType]) : @[],
@([InlineCodeStyle getStyleType]) : @[@([LinkStyle getStyleType]), @([MentionStyle getStyleType])],
@([LinkStyle getStyleType]): @[@([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType]), @([MentionStyle getStyleType])],
@([MentionStyle getStyleType]): @[@([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType])]
@([MentionStyle getStyleType]): @[@([InlineCodeStyle getStyleType]), @([LinkStyle getStyleType])],
@([H1Style getStyleType]): @[@([H2Style getStyleType]), @([H3Style getStyleType])],
@([H2Style getStyleType]): @[@([H1Style getStyleType]), @([H3Style getStyleType])],
@([H3Style getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType])]
};

_blockingStyles = @{
Expand All @@ -107,6 +113,9 @@ - (void)setDefaults {
@([InlineCodeStyle getStyleType]) : @[],
@([LinkStyle getStyleType]): @[],
@([MentionStyle getStyleType]): @[],
@([H1Style getStyleType]): @[],
@([H2Style getStyleType]): @[],
@([H3Style getStyleType]): @[]
};

_editorParser = [[EditorParser alloc] initWithEditor:self];
Expand Down Expand Up @@ -320,15 +329,18 @@ - (CGSize)measureSize:(CGFloat)maxWidth {
// edge case: empty input should still be of a height of a single line, so we add a mock "I" character
if([currentStr length] == 0 ) {
[currentStr appendAttributedString:
[[NSAttributedString alloc] initWithString:@"I" attributes:defaultTypingAttributes]
[[NSAttributedString alloc] initWithString:@"I" attributes:textView.typingAttributes]
];
}

// edge case: trailing newlines aren't counted towards height calculations, so we add a mock "I" character
if([currentStr length] > 0 && [[currentStr.string substringFromIndex:[currentStr length] - 1] isEqualToString:@"\n"]) {
[currentStr appendAttributedString:
[[NSAttributedString alloc] initWithString:@"I" attributes:defaultTypingAttributes]
];
if(currentStr.length > 0) {
unichar lastChar = [currentStr.string characterAtIndex:currentStr.length-1];
if([[NSCharacterSet newlineCharacterSet] characterIsMember:lastChar]) {
[currentStr appendAttributedString:
[[NSAttributedString alloc] initWithString:@"I" attributes:textView.typingAttributes]
];
}
}

CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)currentStr);
Expand Down Expand Up @@ -455,9 +467,9 @@ - (void)tryUpdatingActiveStyles {
.isInlineCode = [_activeStyles containsObject: @([InlineCodeStyle getStyleType])],
.isLink = [_activeStyles containsObject: @([LinkStyle getStyleType])],
.isMention = [_activeStyles containsObject: @([MentionStyle getStyleType])],
.isH1 = NO, // [_activeStyles containsObject: @([H1Style getStyleType])],
.isH2 = NO, // [_activeStyles containsObject: @([H2Style getStyleType])],
.isH3 = NO, // [_activeStyles containsObject: @([H3Style getStyleType])],
.isH1 = [_activeStyles containsObject: @([H1Style getStyleType])],
.isH2 = [_activeStyles containsObject: @([H2Style getStyleType])],
.isH3 = [_activeStyles containsObject: @([H3Style getStyleType])],
.isCodeBlock = NO, // [_activeStyles containsObject: @([CodeBlockStyle getStyleType])],
.isBlockQuote = NO, // [_activeStyles containsObject: @([BlockQuoteStyle getStyleType])],
.isUnorderedList = NO, // [_activeStyles containsObject: @([UnorderedListStyle getStyleType])],
Expand Down Expand Up @@ -518,6 +530,12 @@ - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args {
} else if([commandName isEqualToString:@"startMention"]) {
NSString *indicator = (NSString *)args[0];
[self startMentionWithIndicator:indicator];
} else if([commandName isEqualToString:@"toggleH1"]) {
[self toggleParagraphStyle:[H1Style getStyleType]];
} else if([commandName isEqualToString:@"toggleH2"]) {
[self toggleParagraphStyle:[H2Style getStyleType]];
} else if([commandName isEqualToString:@"toggleH3"]) {
[self toggleParagraphStyle:[H3Style getStyleType]];
}
}

Expand Down Expand Up @@ -606,6 +624,19 @@ - (void)toggleRegularStyle:(StyleType)type {
}
}

- (void)toggleParagraphStyle:(StyleType)type {
id<BaseStyleProtocol> styleClass = stylesDict[@(type)];
// we always pass whole paragraph/s range to these styles
NSRange paragraphRange = [textView.textStorage.string paragraphRangeForRange:textView.selectedRange];

if([self handleStyleBlocksAndConflicts:type range:paragraphRange]) {
[styleClass applyStyle:paragraphRange];
// height needs to be checked as well with paragraph styles
[self tryUpdatingHeight];
[self tryUpdatingActiveStyles];
}
}

- (void)addLinkAt:(NSInteger)start end:(NSInteger)end text:(NSString *)text url:(NSString *)url {
LinkStyle *linkStyleClass = (LinkStyle *)stylesDict[@([LinkStyle getStyleType])];
if(linkStyleClass == nullptr) { return; }
Expand Down
16 changes: 15 additions & 1 deletion ios/parser/EditorParser.mm
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,12 @@ - (NSString *)tagContentForStyle:(NSNumber *)style openingTag:(BOOL)openingTag l
} else {
return @"mention";
}
} else if([style isEqualToNumber:@([H1Style getStyleType])]) {
return @"h1";
} else if([style isEqualToNumber:@([H2Style getStyleType])]) {
return @"h2";
} else if([style isEqualToNumber:@([H3Style getStyleType])]) {
return @"h3";
}
return @"";
}
Expand Down Expand Up @@ -364,7 +370,7 @@ - (NSArray *)getTextAndStylesFromHtml:(NSString *)html {
[styleArr addObject:@([InlineCodeStyle getStyleType])];
} else if([tagName isEqualToString:@"a"]) {
[styleArr addObject:@([LinkStyle getStyleType])];
// cut only url from the href="..." string
// cut only the url from the href="..." string
NSString *url = [params substringWithRange:NSMakeRange(6, params.length - 7)];
stylePair.styleValue = url;
} else if([tagName isEqualToString:@"mention"]) {
Expand Down Expand Up @@ -395,6 +401,14 @@ - (NSArray *)getTextAndStylesFromHtml:(NSString *)html {
mentionParams.attributes = formattedAttrsString;

stylePair.styleValue = mentionParams;
} else if([[tagName substringWithRange:NSMakeRange(0, 1)] isEqualToString: @"h"]) {
if([tagName isEqualToString:@"h1"]) {
[styleArr addObject:@([H1Style getStyleType])];
} else if([tagName isEqualToString:@"h2"]) {
[styleArr addObject:@([H2Style getStyleType])];
} else if([tagName isEqualToString:@"h3"]) {
[styleArr addObject:@([H3Style getStyleType])];
}
}

stylePair.rangeValue = tagRangeValue;
Expand Down
6 changes: 6 additions & 0 deletions ios/styles/H1Style.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#import "StyleHeaders.h"

@implementation H1Style
+ (StyleType)getStyleType { return H1; }
- (CGFloat)getHeadingFontSize { return 32; } // TODO: headings style config
@end
7 changes: 7 additions & 0 deletions ios/styles/H2Style.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#import "StyleHeaders.h"

@implementation H2Style
+ (StyleType)getStyleType { return H2; }
- (CGFloat)getHeadingFontSize { return 24; } // TODO: headings style config
@end

8 changes: 8 additions & 0 deletions ios/styles/H3Style.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#import "StyleHeaders.h"

@implementation H3Style
+ (StyleType)getStyleType { return H3; }
- (CGFloat)getHeadingFontSize { return 20; } // TODO: headings style config
@end


124 changes: 124 additions & 0 deletions ios/styles/HeadingStyleBase.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#import "StyleHeaders.h"
#import "ReactNativeRichTextEditorView.h"
#import "FontExtension.h"
#import "OccurenceUtils.h"

@implementation HeadingStyleBase {
ReactNativeRichTextEditorView *_editor;
}

+ (StyleType)getStyleType { return None; }

- (CGFloat)getHeadingFontSize { return 0; }

- (instancetype)initWithEditor:(id)editor {
self = [super init];
_editor = (ReactNativeRichTextEditorView *) editor;
return self;
}

// the range will already be the full paragraph/s range
// but if the paragraph is empty it still is of length 0
- (void)applyStyle:(NSRange)range {
BOOL isStylePresent = [self detectStyle:range];
if(range.length >= 1) {
isStylePresent ? [self removeAttributes:range] : [self addAttributes:range];
} else {
isStylePresent ? [self removeTypingAttributes] : [self addTypingAttributes];
}
}

// the range will already be the proper full paragraph/s range
- (void)addAttributes:(NSRange)range {
[_editor->textView.textStorage beginEditing];
[_editor->textView.textStorage enumerateAttribute:NSFontAttributeName inRange:range options:0
usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
UIFont *font = (UIFont *)value;
if(font != nullptr) {
UIFont *newFont = [font setSize:[self getHeadingFontSize]];
[_editor->textView.textStorage addAttribute:NSFontAttributeName value:newFont range:range];
}
}
];
[_editor->textView.textStorage endEditing];
}

// will always be called on empty paragraphs so only typing attributes can be changed
- (void)addTypingAttributes {
UIFont *currentFontAttr = (UIFont *)_editor->textView.typingAttributes[NSFontAttributeName];
if(currentFontAttr != nullptr) {
NSMutableDictionary *newTypingAttrs = [_editor->textView.typingAttributes mutableCopy];
newTypingAttrs[NSFontAttributeName] = [currentFontAttr setSize:[self getHeadingFontSize]];
_editor->textView.typingAttributes = newTypingAttrs;
}
}

// we need to remove the style from the whole paragraph
- (void)removeAttributes:(NSRange)range {
NSRange paragraphRange = [_editor->textView.textStorage.string paragraphRangeForRange:range];

[_editor->textView.textStorage beginEditing];
[_editor->textView.textStorage enumerateAttribute:NSFontAttributeName inRange:paragraphRange options:0
usingBlock:^(id _Nullable value, NSRange range, BOOL * _Nonnull stop) {
if([self styleCondition:value :range]) {
UIFont *newFont = [(UIFont *)value setSize:[[_editor->config primaryFontSize] floatValue]];
[_editor->textView.textStorage addAttribute:NSFontAttributeName value:newFont range:range];
}
}
];
[_editor->textView.textStorage endEditing];

// typing attributes still need to be removed
UIFont *currentFontAttr = (UIFont *)_editor->textView.typingAttributes[NSFontAttributeName];
if(currentFontAttr != nullptr) {
NSMutableDictionary *newTypingAttrs = [_editor->textView.typingAttributes mutableCopy];
newTypingAttrs[NSFontAttributeName] = [currentFontAttr setSize:[[_editor->config primaryFontSize] floatValue]];
_editor->textView.typingAttributes = newTypingAttrs;
}
}

- (void)removeTypingAttributes {
// all the heading still needs to be removed because this function may be called in conflicting styles logic
// typing attributes already get removed in there as well
[self removeAttributes:_editor->textView.selectedRange];
}

- (BOOL)styleCondition:(id _Nullable)value :(NSRange)range {
UIFont *font = (UIFont *)value;
return font != nullptr && font.pointSize == [self getHeadingFontSize];
}

- (BOOL)detectStyle:(NSRange)range {
if(range.length >= 1) {
return [OccurenceUtils detect:NSFontAttributeName withEditor:_editor inRange:range
withCondition: ^BOOL(id _Nullable value, NSRange range) {
return [self styleCondition:value :range];
}
];
} else {
UIFont *currentFontAttr = (UIFont *)_editor->textView.typingAttributes[NSFontAttributeName];
if(currentFontAttr == nullptr) {
return false;
}
return currentFontAttr.pointSize == [self getHeadingFontSize];
}
}

- (BOOL)anyOccurence:(NSRange)range {
return [OccurenceUtils any:NSFontAttributeName withEditor:_editor inRange:range
withCondition:^BOOL(id _Nullable value, NSRange range) {
return [self styleCondition:value :range];
}
];
}

- (NSArray<StylePair *> *_Nullable)findAllOccurences:(NSRange)range {
return [OccurenceUtils all:NSFontAttributeName withEditor:_editor inRange:range
withCondition:^BOOL(id _Nullable value, NSRange range) {
return [self styleCondition:value :range];
}
];
}

@end

3 changes: 1 addition & 2 deletions ios/utils/FontExtension.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
#pragma once

@interface UIFont (FontExtension)

- (BOOL)isBold;
- (UIFont *)setBold;
- (UIFont *)removeBold;
Expand All @@ -12,5 +11,5 @@
- (UIFont *)removeItalic;
- (BOOL)isMonospace:(EditorConfig *)withConfig;
- (UIFont *)withFontTraits:(UIFont *)from;

- (UIFont *)setSize:(CGFloat)size;
@end
10 changes: 10 additions & 0 deletions ios/utils/FontExtension.mm
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,14 @@ - (UIFont *)withFontTraits:(UIFont *)from {
return newFont;
}

- (UIFont *)setSize:(CGFloat)size {
UIFontDescriptor *newFontDescriptor = [self.fontDescriptor fontDescriptorWithSize:size];
if(newFontDescriptor != nullptr) {
return [UIFont fontWithDescriptor:newFontDescriptor size:0];
} else {
RCTLogWarn(@"[RichTextEditor]: Couldn't apply heading style to the font.");
return self;
}
}

@end
13 changes: 13 additions & 0 deletions ios/utils/StyleHeaders.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,16 @@
- (NSRange)getFullMentionRangeAt:(NSUInteger)location;
- (NSValue *)getActiveMentionRange;
@end

@interface HeadingStyleBase : NSObject<BaseStyleProtocol>
- (CGFloat)getHeadingFontSize;
@end

@interface H1Style : HeadingStyleBase
@end

@interface H2Style : HeadingStyleBase
@end

@interface H3Style : HeadingStyleBase
@end
1 change: 1 addition & 0 deletions ios/utils/StyleTypeEnum.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#import <UIKit/UIKit.h>

typedef NS_ENUM(NSInteger, StyleType) {
None,
Bold,
Italic,
Underline,
Expand Down
Loading