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
1 change: 1 addition & 0 deletions ios/ReactNativeRichTextEditorView.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
@public EditorConfig *config;
@public NSMutableDictionary<NSAttributedStringKey, id> *defaultTypingAttributes;
@public NSDictionary<NSNumber *, id<BaseStyleProtocol>> *stylesDict;
@public BOOL blockEmitting;
}
- (CGSize)measureSize:(CGFloat)maxWidth;
- (void)emitOnLinkDetectedEvent:(NSString *)text url:(NSString *)url;
Expand Down
57 changes: 41 additions & 16 deletions ios/ReactNativeRichTextEditorView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ - (void)setDefaults {
_recentlyEmittedString = @"";
_recentlyEmittedHtml = @"";
_emitHtml = NO;
blockEmitting = NO;
_emitFocusBlur = YES;

defaultTypingAttributes = [[NSMutableDictionary<NSAttributedStringKey, id> alloc] init];
Expand All @@ -91,7 +92,8 @@ - (void)setDefaults {
@([MentionStyle getStyleType]): [[MentionStyle alloc] initWithEditor:self],
@([H1Style getStyleType]): [[H1Style alloc] initWithEditor:self],
@([H2Style getStyleType]): [[H2Style alloc] initWithEditor:self],
@([H3Style getStyleType]): [[H3Style alloc] initWithEditor:self]
@([H3Style getStyleType]): [[H3Style alloc] initWithEditor:self],
@([UnorderedListStyle getStyleType]): [[UnorderedListStyle alloc] initWithEditor:self]
};

_conflictingStyles = @{
Expand All @@ -102,9 +104,10 @@ - (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])],
@([H2Style getStyleType]): @[@([H1Style getStyleType]), @([H3Style getStyleType])],
@([H3Style getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType])]
@([H1Style getStyleType]): @[@([H2Style getStyleType]), @([H3Style getStyleType]), @([UnorderedListStyle getStyleType])],
@([H2Style getStyleType]): @[@([H1Style getStyleType]), @([H3Style getStyleType]), @([UnorderedListStyle getStyleType])],
@([H3Style getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([UnorderedListStyle getStyleType])],
@([UnorderedListStyle getStyleType]): @[@([H1Style getStyleType]), @([H2Style getStyleType]), @([H3Style getStyleType])],
};

_blockingStyles = @{
Expand All @@ -117,7 +120,8 @@ - (void)setDefaults {
@([MentionStyle getStyleType]): @[],
@([H1Style getStyleType]): @[],
@([H2Style getStyleType]): @[],
@([H3Style getStyleType]): @[]
@([H3Style getStyleType]): @[],
@([UnorderedListStyle getStyleType]): @[],
};

_editorParser = [[EditorParser alloc] initWithEditor:self];
Expand Down Expand Up @@ -205,6 +209,7 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const &
defaultTypingAttributes[NSFontAttributeName] = [config primaryFont];
defaultTypingAttributes[NSUnderlineColorAttributeName] = [config primaryColor];
defaultTypingAttributes[NSStrikethroughColorAttributeName] = [config primaryColor];
defaultTypingAttributes[NSParagraphStyleAttributeName] = [[NSParagraphStyle alloc] init];
textView.typingAttributes = defaultTypingAttributes;

if(stylePropChanged) {
Expand Down Expand Up @@ -496,7 +501,7 @@ - (void)tryUpdatingActiveStyles {
.isH3 = [_activeStyles containsObject: @([H3Style getStyleType])],
.isCodeBlock = NO, // [_activeStyles containsObject: @([CodeBlockStyle getStyleType])],
.isBlockQuote = NO, // [_activeStyles containsObject: @([BlockQuoteStyle getStyleType])],
.isUnorderedList = NO, // [_activeStyles containsObject: @([UnorderedListStyle getStyleType])],
.isUnorderedList = [_activeStyles containsObject: @([UnorderedListStyle getStyleType])],
.isOrderedList = NO, // [_activeStyles containsObject: @([OrderedListStyle getStyleType]]],
.isImage = NO // [_activeStyles containsObject: @([ImageStyle getStyleType]]],
});
Expand Down Expand Up @@ -560,11 +565,13 @@ - (void)handleCommand:(const NSString *)commandName args:(const NSArray *)args {
[self toggleParagraphStyle:[H2Style getStyleType]];
} else if([commandName isEqualToString:@"toggleH3"]) {
[self toggleParagraphStyle:[H3Style getStyleType]];
} else if([commandName isEqualToString:@"toggleUnorderedList"]) {
[self toggleParagraphStyle:[UnorderedListStyle getStyleType]];
}
}

- (std::shared_ptr<ReactNativeRichTextEditorViewEventEmitter>)getEventEmitter {
if(_eventEmitter != nullptr) {
if(_eventEmitter != nullptr && !blockEmitting) {
auto emitter = static_cast<const ReactNativeRichTextEditorViewEventEmitter &>(*_eventEmitter);
return std::make_shared<ReactNativeRichTextEditorViewEventEmitter>(emitter);
} else {
Expand Down Expand Up @@ -644,6 +651,7 @@ - (void)toggleRegularStyle:(StyleType)type {

if([self handleStyleBlocksAndConflicts:type range:textView.selectedRange]) {
[styleClass applyStyle:textView.selectedRange];
[self tryUpdatingHeight];
[self tryUpdatingActiveStyles];
}
}
Expand All @@ -655,7 +663,6 @@ - (void)toggleParagraphStyle:(StyleType)type {

if([self handleStyleBlocksAndConflicts:type range:paragraphRange]) {
[styleClass applyStyle:paragraphRange];
// height needs to be checked as well with paragraph styles
[self tryUpdatingHeight];
[self tryUpdatingActiveStyles];
}
Expand All @@ -669,6 +676,7 @@ - (void)addLinkAt:(NSInteger)start end:(NSInteger)end text:(NSString *)text url:
NSRange linkRange = NSMakeRange(start, end - start);
if([self handleStyleBlocksAndConflicts:[LinkStyle getStyleType] range:linkRange]) {
[linkStyleClass addLink:text url:url range:linkRange manual:YES];
[self tryUpdatingHeight];
[self tryUpdatingActiveStyles];
}
}
Expand All @@ -680,6 +688,7 @@ - (void)addMention:(NSString *)indicator text:(NSString *)text attributes:(NSStr

if([self handleStyleBlocksAndConflicts:[MentionStyle getStyleType] range:[[mentionStyleClass getActiveMentionRange] rangeValue]]) {
[mentionStyleClass addMention:indicator text:text attributes:attributes];
[self tryUpdatingHeight];
[self tryUpdatingActiveStyles];
}
}
Expand All @@ -690,6 +699,7 @@ - (void)startMentionWithIndicator:(NSString *)indicator {

if([self handleStyleBlocksAndConflicts:[MentionStyle getStyleType] range:[[mentionStyleClass getActiveMentionRange] rangeValue]]) {
[mentionStyleClass startMentionWithIndicator:indicator];
[self tryUpdatingHeight];
[self tryUpdatingActiveStyles];
}
}
Expand Down Expand Up @@ -781,14 +791,17 @@ - (void)anyTextMayHaveBeenModified {

// do all the stuff only if the text really changed
if(![textView.textStorage.string isEqualToString:_recentlyEmittedString]) {
// emptying input
if(textView.textStorage.string.length == 0) {
// reset typing attribtues
textView.typingAttributes = defaultTypingAttributes;
}

// placholder management
if(_recentlyEmittedString.length == 0 && textView.textStorage.string.length > 0) {
[self setPlaceholderLabelShown:NO];
} else if(_recentlyEmittedString.length > 0 && textView.textStorage.string.length == 0) {
[self setPlaceholderLabelShown:YES];
// also typing attributes get reset when emptying the input
textView.typingAttributes = defaultTypingAttributes;
}

// modified words handling
Expand Down Expand Up @@ -816,12 +829,12 @@ - (void)anyTextMayHaveBeenModified {

// set the recently emitted string
_recentlyEmittedString = [textView.textStorage.string copy];

// update height on each character change
[self tryUpdatingHeight];
// update active styles as well
[self tryUpdatingActiveStyles];
}

// update height on each character change
[self tryUpdatingHeight];
// update active styles as well
[self tryUpdatingActiveStyles];
}

// MARK: - UITextView delegate methods
Expand Down Expand Up @@ -855,7 +868,19 @@ - (void)textViewDidEndEditing:(UITextView *)textView {

- (bool)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
_recentlyChangedRange = NSMakeRange(range.location, text.length);
return true;

UnorderedListStyle *uStyle = stylesDict[@([UnorderedListStyle getStyleType])];
if(uStyle != nullptr) {
// removing first line list fix
[uStyle handleBackspaceInRange:range replacementText:text];
// creating unordered list from "- "
if([uStyle tryHandlingListShorcutInRange:range replacementText:text]) {
// we successfully added a list -> so we reject the text change
return NO;
}
}

return YES;
}

- (void)textViewDidChangeSelection:(UITextView *)textView {
Expand Down
65 changes: 61 additions & 4 deletions ios/parser/EditorParser.mm
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ - (NSString *)parseToHtml {
NSMutableString *result = [[NSMutableString alloc] initWithString: @"<html>"];
NSSet<NSNumber *>*previousActiveStyles = [[NSSet<NSNumber *> alloc]init];
BOOL newLine = YES;
BOOL inUnorderedList = NO;
unichar lastCharacter = 0;

for(int i = 0; i < _editor->textView.textStorage.length; i++) {
Expand Down Expand Up @@ -58,7 +59,15 @@ - (NSString *)parseToHtml {
}

// append closing paragraph tag
[result appendString:@"</p>"];
if([previousActiveStyles containsObject:@([UnorderedListStyle getStyleType])] ||
[previousActiveStyles containsObject:@([H1Style getStyleType])] ||
[previousActiveStyles containsObject:@([H2Style getStyleType])] ||
[previousActiveStyles containsObject:@([H3Style getStyleType])]
) {
// do nothing, proper closing paragraph tags have been already appended
} else {
[result appendString:@"</p>"];
}

// clear the previous styles
previousActiveStyles = [[NSSet<NSNumber *> alloc]init];
Expand All @@ -69,7 +78,29 @@ - (NSString *)parseToHtml {
// new line - open the paragraph
if(newLine) {
newLine = NO;
[result appendString:@"\n<p>"];

// handle ending lists
if(inUnorderedList && ![currentActiveStyles containsObject:@([UnorderedListStyle getStyleType])]) {
inUnorderedList = NO;
[result appendString:@"\n</ul>"];
}

// handle starting lists
if(!inUnorderedList && [currentActiveStyles containsObject:@([UnorderedListStyle getStyleType])]) {
inUnorderedList = YES;
[result appendString:@"\n<ul>"];
}

// don't add the <p> tag if paragraph styles are present
if([currentActiveStyles containsObject:@([UnorderedListStyle getStyleType])] ||
[currentActiveStyles containsObject:@([H1Style getStyleType])] ||
[currentActiveStyles containsObject:@([H2Style getStyleType])] ||
[currentActiveStyles containsObject:@([H3Style getStyleType])]
) {
[result appendString:@"\n"];
} else {
[result appendString:@"\n<p>"];
}
}

// get styles that have ended: they are sorted in an ascending manner
Expand Down Expand Up @@ -117,7 +148,18 @@ - (NSString *)parseToHtml {
}

// finish the paragraph
[result appendString:@"</p>"];
// handle ending paragraph styles
if([previousActiveStyles containsObject:@([UnorderedListStyle getStyleType])]) {
[result appendString:@"\n</ul>"];
} else if(
[previousActiveStyles containsObject:@([H1Style getStyleType])] ||
[previousActiveStyles containsObject:@([H2Style getStyleType])] ||
[previousActiveStyles containsObject:@([H3Style getStyleType])]
) {
// do nothing, heading closing tag has already ben appended
} else {
[result appendString:@"</p>"];
}
}

[result appendString: @"\n</html>"];
Expand Down Expand Up @@ -183,6 +225,8 @@ - (NSString *)tagContentForStyle:(NSNumber *)style openingTag:(BOOL)openingTag l
return @"h2";
} else if([style isEqualToNumber:@([H3Style getStyleType])]) {
return @"h3";
} else if([style isEqualToNumber:@([UnorderedListStyle getStyleType])]) {
return @"li";
}
return @"";
}
Expand Down Expand Up @@ -289,7 +333,7 @@ - (NSArray *)getTextAndStylesFromHtml:(NSString *)html {
gettingTagName = NO;
gettingTagParams = NO;

if([currentTagName isEqualToString:@"p"] || [currentTagName isEqualToString:@"br"]) {
if([currentTagName isEqualToString:@"p"] || [currentTagName isEqualToString:@"br"] || [currentTagName isEqualToString:@"li"]) {
// do nothing, we don't include these tags in styles
} else if(!closingTag) {
// we finish opening tag - get its location and optionally params and put them under tag name key in ongoingTags
Expand All @@ -299,8 +343,19 @@ - (NSArray *)getTextAndStylesFromHtml:(NSString *)html {
[tagArr addObject:[currentTagParams copy]];
}
ongoingTags[currentTagName] = tagArr;

// skip one newline after lists' opening tags
if([currentTagName isEqualToString:@"ul"]) {
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 lists' closing tags
if([currentTagName isEqualToString:@"ul"]) {
plainText = [[plainText substringWithRange: NSMakeRange(0, plainText.length - 1)] mutableCopy];
}

NSMutableArray *tagEntry = [[NSMutableArray alloc] init];

NSArray *tagData = ongoingTags[currentTagName];
Expand Down Expand Up @@ -409,6 +464,8 @@ - (NSArray *)getTextAndStylesFromHtml:(NSString *)html {
} else if([tagName isEqualToString:@"h3"]) {
[styleArr addObject:@([H3Style getStyleType])];
}
} else if([tagName isEqualToString:@"ul"]) {
[styleArr addObject:@([UnorderedListStyle getStyleType])];
}

stylePair.rangeValue = tagRangeValue;
Expand Down
1 change: 0 additions & 1 deletion ios/styles/H2Style.mm
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,3 @@ @implementation H2Style
+ (StyleType)getStyleType { return H2; }
- (CGFloat)getHeadingFontSize { return 24; } // TODO: headings style config
@end

2 changes: 0 additions & 2 deletions ios/styles/H3Style.mm
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,3 @@ @implementation H3Style
+ (StyleType)getStyleType { return H3; }
- (CGFloat)getHeadingFontSize { return 20; } // TODO: headings style config
@end


3 changes: 3 additions & 0 deletions ios/styles/HeadingStyleBase.mm
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ - (void)addAttributes:(NSRange)range {
}
];
[_editor->textView.textStorage endEditing];

// also toggle typing attributes
[self addTypingAttributes];
}

// will always be called on empty paragraphs so only typing attributes can be changed
Expand Down
Loading
Loading