Skip to content

Commit cc9db06

Browse files
authored
fix: handle replacement edge cases for links and mentions (#123)
1 parent 8e2f9e5 commit cc9db06

6 files changed

Lines changed: 55 additions & 10 deletions

File tree

ios/ReactNativeRichTextEditorView.mm

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,6 +1083,18 @@ - (bool)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range r
10831083
rejectTextChanges = rejectTextChanges || removedFirstLineList || addedShortcutList;
10841084
}
10851085

1086+
LinkStyle *linkStyle = stylesDict[@([LinkStyle getStyleType])];
1087+
if(linkStyle != nullptr) {
1088+
BOOL fixedLeadingAttributes = [linkStyle handleLeadingLinkReplacement:range replacementText:text];
1089+
rejectTextChanges = rejectTextChanges || fixedLeadingAttributes;
1090+
}
1091+
1092+
MentionStyle *mentionStyle = stylesDict[@([MentionStyle getStyleType])];
1093+
if(mentionStyle != nullptr) {
1094+
BOOL fixedLeadingAttributes = [mentionStyle handleLeadingMentionReplacement:range replacementText:text];
1095+
rejectTextChanges = rejectTextChanges || fixedLeadingAttributes;
1096+
}
1097+
10861098
if(rejectTextChanges) {
10871099
[self anyTextMayHaveBeenModified];
10881100
}

ios/styles/LinkStyle.mm

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,21 @@ - (void)handleManualLinks:(NSString *)word inRange:(NSRange)wordRange {
407407
[self manageLinkTypingAttributes];
408408
}
409409

410+
// replacing whole input (that starts with a link) with a manually typed letter improperly applies link's attributes to all the following text
411+
- (BOOL)handleLeadingLinkReplacement:(NSRange)range replacementText:(NSString *)text {
412+
// whole textView range gets replaced with a single letter
413+
if(_editor->textView.textStorage.string.length > 0 && NSEqualRanges(range, NSMakeRange(0, _editor->textView.textStorage.string.length)) && text.length == 1) {
414+
// first character detection is enough for the removal to be done
415+
if([self detectStyle:NSMakeRange(0, 1)]) {
416+
[self removeAttributes:NSMakeRange(0, _editor->textView.textStorage.string.length)];
417+
// do the replacing manually
418+
[TextInsertionUtils replaceText:text inView:_editor->textView at:range additionalAttributes:nullptr editor:_editor];
419+
return YES;
420+
}
421+
}
422+
return NO;
423+
}
424+
410425
// MARK: - Private non-standard methods
411426

412427
// determines whether a given range contains only links pointing to one url

ios/styles/MentionStyle.mm

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,21 @@ - (void)manageMentionTypingAttributes {
390390
}
391391
}
392392

393+
// replacing whole input (that starts with a mention) with a manually typed letter improperly applies mention's attributes to all the following text
394+
- (BOOL)handleLeadingMentionReplacement:(NSRange)range replacementText:(NSString *)text {
395+
// whole textView range gets replaced with a single letter
396+
if(_editor->textView.textStorage.string.length > 0 && NSEqualRanges(range, NSMakeRange(0, _editor->textView.textStorage.string.length)) && text.length == 1) {
397+
// first character detection is enough for the removal to be done
398+
if([self detectStyle:NSMakeRange(0, 1)]) {
399+
[self removeAttributes:NSMakeRange(0, _editor->textView.textStorage.string.length)];
400+
// do the replacing manually
401+
[TextInsertionUtils replaceText:text inView:_editor->textView at:range additionalAttributes:nullptr editor:_editor];
402+
return YES;
403+
}
404+
}
405+
return NO;
406+
}
407+
393408
// returns mention params if it exists
394409
- (MentionParams *)getMentionParamsAt:(NSUInteger)location {
395410
NSRange mentionRange = NSMakeRange(0, 0);

ios/utils/StyleHeaders.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
- (void)manageLinkTypingAttributes;
2727
- (void)handleAutomaticLinks:(NSString *)word inRange:(NSRange)wordRange;
2828
- (void)handleManualLinks:(NSString *)word inRange:(NSRange)wordRange;
29+
- (BOOL)handleLeadingLinkReplacement:(NSRange)range replacementText:(NSString *)text;
2930
@end
3031

3132
@interface MentionStyle : NSObject<BaseStyleProtocol>
@@ -35,6 +36,7 @@
3536
- (void)handleExistingMentions;
3637
- (void)manageMentionEditing;
3738
- (void)manageMentionTypingAttributes;
39+
- (BOOL)handleLeadingMentionReplacement:(NSRange)range replacementText:(NSString *)text;
3840
- (MentionParams *)getMentionParamsAt:(NSUInteger)location;
3941
- (NSRange)getFullMentionRangeAt:(NSUInteger)location;
4042
- (NSValue *)getActiveMentionRange;

ios/utils/TextInsertionUtils.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#import <UIKit/UIKit.h>
2-
#import "ReactNativeRichTextEditorView.h"
32

43
@interface TextInsertionUtils : NSObject
5-
+ (void)insertText:(NSString*)text inView:(UITextView*)textView at:(NSInteger)index additionalAttributes:(NSDictionary<NSAttributedStringKey, id>*)additionalAttrs editor:(ReactNativeRichTextEditorView *)editor;
6-
+ (void)replaceText:(NSString*)text inView:(UITextView*)textView at:(NSRange)range additionalAttributes:(NSDictionary<NSAttributedStringKey, id>*)additionalAttrs editor:(ReactNativeRichTextEditorView *)editor;
4+
+ (void)insertText:(NSString*)text inView:(UITextView*)textView at:(NSInteger)index additionalAttributes:(NSDictionary<NSAttributedStringKey, id>*)additionalAttrs editor:(id)editor;
5+
+ (void)replaceText:(NSString*)text inView:(UITextView*)textView at:(NSRange)range additionalAttributes:(NSDictionary<NSAttributedStringKey, id>*)additionalAttrs editor:(id)editor;
76
@end

ios/utils/TextInsertionUtils.mm

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#import "TextInsertionUtils.h"
22
#import "UIView+React.h"
3-
3+
#import "ReactNativeRichTextEditorView.h"
44

55
@implementation TextInsertionUtils
6-
+ (void)insertText:(NSString*)text inView:(UITextView*)textView at:(NSInteger)index additionalAttributes:(NSDictionary<NSAttributedStringKey, id>*)additionalAttrs editor:(ReactNativeRichTextEditorView *)editor {
6+
+ (void)insertText:(NSString*)text inView:(UITextView*)textView at:(NSInteger)index additionalAttributes:(NSDictionary<NSAttributedStringKey, id>*)additionalAttrs editor:(id)editor {
77
NSMutableDictionary<NSAttributedStringKey, id> *copiedAttrs = [textView.typingAttributes mutableCopy];
88
if(additionalAttrs != nullptr) {
99
[copiedAttrs addEntriesFromDictionary: additionalAttrs];
@@ -15,12 +15,13 @@ + (void)insertText:(NSString*)text inView:(UITextView*)textView at:(NSInteger)in
1515
[textView reactFocus];
1616
textView.selectedRange = NSMakeRange(index + text.length, 0);
1717

18-
if(editor != nullptr) {
19-
editor->recentlyChangedRange = NSMakeRange(index, text.length);
18+
ReactNativeRichTextEditorView *typedEditor = (ReactNativeRichTextEditorView *)editor;
19+
if(typedEditor != nullptr) {
20+
typedEditor->recentlyChangedRange = NSMakeRange(index, text.length);
2021
}
2122
}
2223

23-
+ (void)replaceText:(NSString*)text inView:(UITextView*)textView at:(NSRange)range additionalAttributes:(NSDictionary<NSAttributedStringKey, id>*)additionalAttrs editor:(ReactNativeRichTextEditorView *)editor {
24+
+ (void)replaceText:(NSString*)text inView:(UITextView*)textView at:(NSRange)range additionalAttributes:(NSDictionary<NSAttributedStringKey, id>*)additionalAttrs editor:(id)editor {
2425
[textView.textStorage replaceCharactersInRange:range withString:text];
2526
if(additionalAttrs != nullptr) {
2627
[textView.textStorage addAttributes:additionalAttrs range:NSMakeRange(range.location, [text length])];
@@ -29,8 +30,9 @@ + (void)replaceText:(NSString*)text inView:(UITextView*)textView at:(NSRange)ran
2930
[textView reactFocus];
3031
textView.selectedRange = NSMakeRange(range.location + text.length, 0);
3132

32-
if(editor != nullptr) {
33-
editor->recentlyChangedRange = NSMakeRange(range.location, text.length);
33+
ReactNativeRichTextEditorView *typedEditor = (ReactNativeRichTextEditorView *)editor;
34+
if(typedEditor != nullptr) {
35+
typedEditor->recentlyChangedRange = NSMakeRange(range.location, text.length);
3436
}
3537
}
3638
@end

0 commit comments

Comments
 (0)