Skip to content

Commit e6749de

Browse files
authored
Merge branch 'main' into feat/add-scrollEnabled-prop
2 parents 4467366 + 6996dfd commit e6749de

8 files changed

Lines changed: 97 additions & 35 deletions

File tree

android/src/main/java/com/swmansion/enriched/EnrichedTextInputView.kt

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ class EnrichedTextInputView : AppCompatEditText {
7171
private var fontFamily: String? = null
7272
private var fontStyle: Int = ReactConstants.UNSET
7373
private var fontWeight: Int = ReactConstants.UNSET
74+
private var defaultValue: CharSequence? = null
75+
private var defaultValueDirty: Boolean = false
7476

7577
private var inputMethodManager: InputMethodManager? = null
7678

@@ -369,7 +371,24 @@ class EnrichedTextInputView : AppCompatEditText {
369371
return false
370372
}
371373

372-
fun updateTypeface() {
374+
fun afterUpdateTransaction() {
375+
updateTypeface()
376+
updateDefaultValue()
377+
}
378+
379+
fun setDefaultValue(value: CharSequence?) {
380+
defaultValue = value
381+
defaultValueDirty = true
382+
}
383+
384+
private fun updateDefaultValue() {
385+
if (!defaultValueDirty) return
386+
387+
defaultValueDirty = false
388+
setValue(defaultValue ?: "")
389+
}
390+
391+
private fun updateTypeface() {
373392
if (!typefaceDirty) return
374393
typefaceDirty = false
375394

android/src/main/java/com/swmansion/enriched/EnrichedTextInputViewManager.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class EnrichedTextInputViewManager : SimpleViewManager<EnrichedTextInputView>(),
7777

7878
@ReactProp(name = "defaultValue")
7979
override fun setDefaultValue(view: EnrichedTextInputView?, value: String?) {
80-
view?.setValue(value)
80+
view?.setDefaultValue(value)
8181
}
8282

8383
@ReactProp(name = "placeholder")
@@ -161,7 +161,7 @@ class EnrichedTextInputViewManager : SimpleViewManager<EnrichedTextInputView>(),
161161

162162
override fun onAfterUpdateTransaction(view: EnrichedTextInputView) {
163163
super.onAfterUpdateTransaction(view)
164-
view.updateTypeface()
164+
view.afterUpdateTransaction()
165165
}
166166

167167
override fun setPadding(

android/src/main/java/com/swmansion/enriched/styles/ParametrizedStyles.kt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,31 @@ class ParametrizedStyles(private val view: EnrichedTextInputView) {
104104
return Triple(result, start, end)
105105
}
106106

107+
private fun canLinkBeApplied(): Boolean {
108+
val mergingConfig = EnrichedSpans.getMergingConfigForStyle(EnrichedSpans.LINK, view.htmlStyle)?: return true
109+
val conflictingStyles = mergingConfig.conflictingStyles
110+
val blockingStyles = mergingConfig.blockingStyles
111+
112+
for (style in blockingStyles) {
113+
if (view.spanState?.getStart(style) != null) return false
114+
}
115+
116+
for (style in conflictingStyles) {
117+
if (view.spanState?.getStart(style) != null) return false
118+
}
119+
120+
return true
121+
}
122+
107123
private fun afterTextChangedLinks(result: Triple<String, Int, Int>) {
108124
// Do not detect link if it's applied manually
109-
if (isSettingLinkSpan) return
125+
if (isSettingLinkSpan || !canLinkBeApplied()) return
126+
110127
val spannable = view.text as Spannable
111128
val (word, start, end) = result
112129

113130
// TODO: Consider using more reliable regex, this one matches almost anything
114131
val urlPattern = android.util.Patterns.WEB_URL.matcher(word)
115-
116132
val spans = spannable.getSpans(start, end, EnrichedLinkSpan::class.java)
117133
for (span in spans) {
118134
spannable.removeSpan(span)

ios/EnrichedTextInputView.mm

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,9 @@ - (void)tryUpdatingActiveStyles {
605605
// style updates are emitted only if something differs from the previously active styles
606606
BOOL updateNeeded = NO;
607607

608+
// active styles are kept in a separate set until we're sure they can be emitted
609+
NSMutableSet *newActiveStyles = [_activeStyles mutableCopy];
610+
608611
// data for onLinkDetected event
609612
LinkData *detectedLinkData;
610613
NSRange detectedLinkRange = NSMakeRange(0, 0);
@@ -615,14 +618,14 @@ - (void)tryUpdatingActiveStyles {
615618

616619
for (NSNumber* type in stylesDict) {
617620
id<BaseStyleProtocol> style = stylesDict[type];
618-
BOOL wasActive = [_activeStyles containsObject: type];
621+
BOOL wasActive = [newActiveStyles containsObject: type];
619622
BOOL isActive = [style detectStyle:textView.selectedRange];
620623
if(wasActive != isActive) {
621624
updateNeeded = YES;
622625
if(isActive) {
623-
[_activeStyles addObject:type];
626+
[newActiveStyles addObject:type];
624627
} else {
625-
[_activeStyles removeObject:type];
628+
[newActiveStyles removeObject:type];
626629
}
627630
}
628631

@@ -682,6 +685,9 @@ - (void)tryUpdatingActiveStyles {
682685
if(updateNeeded) {
683686
auto emitter = [self getEventEmitter];
684687
if(emitter != nullptr) {
688+
// update activeStyles only if emitter is available
689+
_activeStyles = newActiveStyles;
690+
685691
emitter->onChangeState({
686692
.isBold = [_activeStyles containsObject: @([BoldStyle getStyleType])],
687693
.isItalic = [_activeStyles containsObject: @([ItalicStyle getStyleType])],
@@ -994,6 +1000,9 @@ - (void)manageSelectionBasedChanges {
9941000
textView.typingAttributes = defaultTypingAttributes;
9951001
}
9961002
}
1003+
1004+
// update active styles as well
1005+
[self tryUpdatingActiveStyles];
9971006
}
9981007

9991008
- (void)handleWordModificationBasedChanges:(NSString*)word inRange:(NSRange)range {
@@ -1109,17 +1118,18 @@ - (void)_performRelayout
11091118
if (!textView) { return; }
11101119

11111120
dispatch_async(dispatch_get_main_queue(), ^{
1112-
NSRange wholeRange = NSMakeRange(0, textView.textStorage.string.length);
1121+
NSRange wholeRange = NSMakeRange(0, self->textView.textStorage.string.length);
11131122
NSRange actualRange = NSMakeRange(0, 0);
1114-
[textView.layoutManager invalidateLayoutForCharacterRange:wholeRange actualCharacterRange:&actualRange];
1115-
[textView.layoutManager ensureLayoutForCharacterRange:actualRange];
1116-
[textView.layoutManager invalidateDisplayForCharacterRange:wholeRange];
1123+
[self->textView.layoutManager invalidateLayoutForCharacterRange:wholeRange actualCharacterRange:&actualRange];
1124+
[self->textView.layoutManager ensureLayoutForCharacterRange:actualRange];
1125+
[self->textView.layoutManager invalidateDisplayForCharacterRange:wholeRange];
11171126
});
11181127
}
11191128

11201129
- (void)didMoveToWindow {
11211130
[super didMoveToWindow];
1122-
[self scheduleRelayoutIfNeeded];
1131+
// used to run all lifecycle callbacks
1132+
[self anyTextMayHaveBeenModified];
11231133
}
11241134

11251135
// MARK: - UITextView delegate methods
@@ -1204,9 +1214,6 @@ - (void)textViewDidChangeSelection:(UITextView *)textView {
12041214

12051215
// manage selection changes
12061216
[self manageSelectionBasedChanges];
1207-
1208-
// update active styles
1209-
[self tryUpdatingActiveStyles];
12101217
}
12111218

12121219
// this function isn't called always when some text changes (for example setting link or starting mention with indicator doesn't fire it)

ios/inputParser/InputParser.mm

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#import "StyleHeaders.h"
44
#import "UIView+React.h"
55
#import "TextInsertionUtils.h"
6+
#import "StringExtension.h"
67

78
@implementation InputParser {
89
EnrichedTextInputView *_input;
@@ -204,8 +205,8 @@ - (NSString *)parseToHtmlFromRange:(NSRange)range {
204205
[result appendString: [NSString stringWithFormat:@"<%@>", tagContent]];
205206
}
206207

207-
// append the letter
208-
[result appendString:currentCharacterStr];
208+
// append the letter and escape it if needed
209+
[result appendString: [NSString stringByEscapingHtml:currentCharacterStr]];
209210

210211
// save current styles for next character's checks
211212
previousActiveStyles = currentActiveStyles;
@@ -490,6 +491,7 @@ - (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml {
490491
BOOL closingTag = NO;
491492
NSMutableString *currentTagName = [[NSMutableString alloc] initWithString:@""];
492493
NSMutableString *currentTagParams = [[NSMutableString alloc] initWithString:@""];
494+
NSDictionary *htmlEntitiesDict = [NSString getEscapedCharactersInfoFrom:fixedHtml];
493495

494496
// firstly, extract text and initially processed tags
495497
for(int i = 0; i < fixedHtml.length; i++) {
@@ -511,7 +513,7 @@ - (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml {
511513
} else if(!closingTag) {
512514
// we finish opening tag - get its location and optionally params and put them under tag name key in ongoingTags
513515
NSMutableArray *tagArr = [[NSMutableArray alloc] init];
514-
[tagArr addObject:[NSNumber numberWithInt:plainText.length]];
516+
[tagArr addObject:[NSNumber numberWithInteger:plainText.length]];
515517
if(currentTagParams.length > 0) {
516518
[tagArr addObject:[currentTagParams copy]];
517519
}
@@ -550,8 +552,19 @@ - (NSArray *)getTextAndStylesFromHtml:(NSString *)fixedHtml {
550552
currentTagParams = [[NSMutableString alloc] initWithString:@""];
551553
} else {
552554
if(!insideTag) {
553-
// no tags logic - just append text
554-
[plainText appendString:currentCharacterStr];
555+
// no tags logic - just append the right text
556+
557+
// html entity on the index; use unescaped character and forward iterator accordingly
558+
NSArray *entityInfo = htmlEntitiesDict[@(i)];
559+
if(entityInfo != nullptr) {
560+
NSString *escaped = entityInfo[0];
561+
NSString *unescaped = entityInfo[1];
562+
[plainText appendString:unescaped];
563+
// the iterator will forward by 1 itself
564+
i += escaped.length - 1;
565+
} else {
566+
[plainText appendString:currentCharacterStr];
567+
}
555568
} else {
556569
if(gettingTagName) {
557570
if(currentCharacterChar == ' ') {

ios/inputTextView/InputTextView.mm

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ - (void)copy:(id)sender {
1414
NSString *plainText = [typedInput->textView.textStorage.string substringWithRange:typedInput->textView.selectedRange];
1515
NSString *fixedPlainText = [plainText stringByReplacingOccurrencesOfString:@"\u200B" withString:@""];
1616

17-
NSString *escapedHtml = [NSString stringByEscapingHtml:[typedInput->parser parseToHtmlFromRange:typedInput->textView.selectedRange]];
17+
NSString *parsedHtml = [typedInput->parser parseToHtmlFromRange:typedInput->textView.selectedRange];
1818

1919
NSMutableAttributedString *attrStr = [[typedInput->textView.textStorage attributedSubstringFromRange:typedInput->textView.selectedRange] mutableCopy];
2020
NSRange fullAttrStrRange = NSMakeRange(0, attrStr.length);
@@ -28,7 +28,7 @@ - (void)copy:(id)sender {
2828
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
2929
[pasteboard setItems:@[@{
3030
UTTypeUTF8PlainText.identifier : fixedPlainText,
31-
UTTypeHTML.identifier : escapedHtml,
31+
UTTypeHTML.identifier : parsedHtml,
3232
UTTypeRTF.identifier : rtfData
3333
}]];
3434
}
@@ -53,9 +53,7 @@ - (void)paste:(id)sender {
5353
htmlString = htmlValue;
5454
}
5555

56-
// unescape the html
57-
htmlString = [NSString stringByUnescapingHtml:htmlString];
58-
// validate it
56+
// validate the html
5957
NSString *initiallyProcessedHtml = [typedInput->parser initiallyProcessHtml:htmlString];
6058

6159
if(initiallyProcessedHtml != nullptr) {

ios/utils/StringExtension.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
- (std::string)toCppString;
77
+ (NSString *)fromCppString:(std::string)string;
88
+ (NSString *)stringByEscapingHtml:(NSString *)html;
9-
+ (NSString *)stringByUnescapingHtml:(NSString *)html;
9+
+ (NSDictionary *)getEscapedCharactersInfoFrom:(NSString *)text;
1010
@end
1111

1212
@interface NSMutableString (StringExtension)

ios/utils/StringExtension.mm

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ + (NSString *)stringByEscapingHtml:(NSString *)html {
1616
@"&": @"&amp;",
1717
@"<": @"&lt;",
1818
@">": @"&gt;",
19-
@"\"": @"&quot;",
20-
@"'": @"&apos;"
2119
};
2220

2321
for(NSString *key in escapeMap) {
@@ -26,20 +24,31 @@ + (NSString *)stringByEscapingHtml:(NSString *)html {
2624
return escaped;
2725
}
2826

29-
+ (NSString *)stringByUnescapingHtml:(NSString *)html {
30-
NSMutableString *unescaped = [html mutableCopy];
27+
+ (NSDictionary *)getEscapedCharactersInfoFrom:(NSString *)text {
3128
NSDictionary *unescapeMap = @{
3229
@"&amp;": @"&",
3330
@"&lt;": @"<",
3431
@"&gt;": @">",
35-
@"&quot;": @"\"",
36-
@"&apos;": @"'",
3732
};
3833

34+
NSMutableDictionary *results = [[NSMutableDictionary alloc] init];
35+
3936
for(NSString *key in unescapeMap) {
40-
[unescaped replaceOccurrencesOfString:key withString:unescapeMap[key] options:NSLiteralSearch range:NSMakeRange(0, unescaped.length)];
37+
NSRange searchRange = NSMakeRange(0, text.length);
38+
NSRange foundRange;
39+
40+
while(searchRange.location < text.length) {
41+
foundRange = [text rangeOfString:key options:0 range:searchRange];
42+
if(foundRange.location == NSNotFound) {
43+
break;
44+
}
45+
results[@(foundRange.location)] = @[key, unescapeMap[key]];
46+
searchRange.location = foundRange.location + foundRange.length;
47+
searchRange.length = text.length - searchRange.location;
48+
}
4149
}
42-
return unescaped;
50+
51+
return results;
4352
}
4453

4554
@end

0 commit comments

Comments
 (0)