Skip to content
Open
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
73 changes: 73 additions & 0 deletions .maestro/enrichedInput/flows/links_visual.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
appId: swmansion.enriched.example
---
# Verifies typing a URL directly into the editor
- launchApp

- tapOn:
id: 'toggle-screen-button'

- tapOn:
id: "editor-input"

# autolinks break on text change or paste
- inputText: 'swmansion.com'

- doubleTapOn:
id: 'editor-input'
point: '20%, 50%'
- tapOn:
text: 'Copy'

- tapOn:
id: 'editor-input'
point: '70%, 50%'
- pressKey: Enter

- longPressOn:
id: 'editor-input'
point: '50%, 70%'
- tapOn:
text: 'Paste'
- inputText: 'm'

- pressKey: Enter
- inputText: 'swm'
- longPressOn:
id: 'editor-input'
point: '50%, 75%'
- tapOn:
text: 'Paste'

- runFlow:
file: '../subflows/capture_or_assert_screenshot.yaml'
env:
SCREENSHOT_NAME: 'links_visual_auto'

# manual link where href == textValue but does not match the linkRegex is considered as a manual link
- runFlow:
file: '../subflows/set_editor_value.yaml'
env:
VALUE: '<html><p><a href="example.com">example.com</a></p></html>'

- doubleTapOn:
id: 'editor-input'
point: '20%, 50%'
- tapOn:
text: 'Copy'

- tapOn:
id: 'editor-input'
point: '70%, 50%'
- pressKey: Enter

- longPressOn:
id: 'editor-input'
point: '50%, 70%'
- tapOn:
text: 'Paste'
- pressKey: Backspace

- runFlow:
file: '../subflows/capture_or_assert_screenshot.yaml'
env:
SCREENSHOT_NAME: 'links_visual_manual'
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,11 @@ private static class HtmlParser {
}

public static <T> Spanned fromHtml(String source, T style, EnrichedSpanFactory<T> spanFactory) {
return fromHtml(source, style, spanFactory, null);
}

public static <T> Spanned fromHtml(
String source, T style, EnrichedSpanFactory<T> spanFactory, Pattern linkRegex) {
Parser parser = new Parser();
try {
parser.setProperty(Parser.schemaProperty, HtmlParser.schema);
Expand All @@ -74,7 +79,7 @@ public static <T> Spanned fromHtml(String source, T style, EnrichedSpanFactory<T
throw new RuntimeException(e);
}
HtmlToSpannedConverter converter =
new HtmlToSpannedConverter(source, style, parser, spanFactory);
new HtmlToSpannedConverter(source, style, parser, spanFactory, linkRegex);
return converter.convert();
}

Expand Down Expand Up @@ -408,6 +413,7 @@ class HtmlToSpannedConverter<T> implements ContentHandler {
private final String mSource;
private final XMLReader mReader;
private final SpannableStringBuilder mSpannableStringBuilder;
private final Pattern mLinkRegex;
private static Integer currentOrderedListItemIndex = 0;
private static Boolean isInOrderedList = false;
private static Boolean isInCheckboxList = false;
Expand All @@ -432,12 +438,17 @@ private static void pushAlignmentMark(Editable text, Attributes attributes) {
}

public HtmlToSpannedConverter(
String source, T style, Parser parser, EnrichedSpanFactory<T> spanFactory) {
String source,
T style,
Parser parser,
EnrichedSpanFactory<T> spanFactory,
Pattern linkRegex) {
mStyle = style;
mSource = source;
mSpannableStringBuilder = new SpannableStringBuilder();
mReader = parser;
mSpanFactory = spanFactory;
mLinkRegex = linkRegex;
}

public Spanned convert() {
Expand Down Expand Up @@ -602,7 +613,7 @@ private void handleEndTag(String tag) {
} else if (tag.equalsIgnoreCase("codeblock")) {
endCodeBlock(mSpannableStringBuilder, mStyle, mSpanFactory);
} else if (tag.equalsIgnoreCase("a")) {
endA(mSpannableStringBuilder, mStyle, mSpanFactory);
endA(mSpannableStringBuilder, mStyle, mSpanFactory, mLinkRegex);
} else if (tag.equalsIgnoreCase("u")) {
end(mSpannableStringBuilder, Underline.class, mSpanFactory.createUnderlineSpan(mStyle));
} else if (tag.equalsIgnoreCase("s")) {
Expand Down Expand Up @@ -862,12 +873,18 @@ private static void startA(Editable text, Attributes attributes) {
start(text, new Href(href));
}

private static <T> void endA(Editable text, T style, EnrichedSpanFactory<T> spanFactory) {
private static boolean urlMatchesLinkRegex(String url, Pattern linkRegex) {
if (linkRegex == null) return false;
return linkRegex.matcher(url).matches();
}
Comment thread
hejsztynx marked this conversation as resolved.

private static <T> void endA(
Editable text, T style, EnrichedSpanFactory<T> spanFactory, Pattern linkRegex) {
Href h = getLast(text, Href.class);
if (h != null) {
if (h.mHref != null) {
setSpanFromMark(text, h, spanFactory.createLinkSpan(h.mHref, style));
}
if (h != null && h.mHref != null) {
String linkText = text.subSequence(text.getSpanStart(h), text.length()).toString();
boolean isManual = !linkText.equals(h.mHref) || !urlMatchesLinkRegex(h.mHref, linkRegex);
setSpanFromMark(text, h, spanFactory.createLinkSpan(h.mHref, style, isManual));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ interface EnrichedSpanFactory<T> {
fun createLinkSpan(
url: String,
style: T,
isManual: Boolean,
): EnrichedLinkSpan

fun createMentionSpan(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class EnrichedTextSpanFactory : EnrichedSpanFactory<EnrichedTextStyle> {
override fun createLinkSpan(
url: String,
style: EnrichedTextStyle,
isManual: Boolean,
) = EnrichedTextLinkSpan(url, style)

override fun createMentionSpan(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ class EnrichedTextInputSpannableFactory : EnrichedSpanFactory<HtmlStyle> {
override fun createLinkSpan(
url: String,
style: HtmlStyle,
) = EnrichedInputLinkSpan(url, style, true)
isManual: Boolean,
) = EnrichedInputLinkSpan(url, style, isManual)

override fun createMentionSpan(
text: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.graphics.Color
import android.graphics.Rect
import android.graphics.text.LineBreaker
import android.os.Build
import android.text.Editable
import android.text.InputType
import android.text.Spannable
import android.text.SpannableString
Expand Down Expand Up @@ -398,8 +399,9 @@ class EnrichedTextInputView :
val pasteEnd = (start + insertedLength).coerceIn(0, finalText.length)
setSelection(pasteEnd)

// Detect links in the newly pasted range
parametrizedStyles?.detectLinksInRange(finalText, start.coerceAtMost(pasteEnd), pasteEnd)
// Update links and mentions in the newly pasted range
val editable = text as? Editable ?: return
parametrizedStyles?.afterTextChanged(editable, start.coerceAtMost(pasteEnd), pasteEnd)
}

fun requestFocusProgrammatically() {
Expand All @@ -413,7 +415,7 @@ class EnrichedTextInputView :
val normalized = GumboNormalizer.normalizeHtml(text.toString()) ?: return text

return try {
val parsed = EnrichedParser.fromHtml(normalized, htmlStyle, spannableFactory)
val parsed = EnrichedParser.fromHtml(normalized, htmlStyle, spannableFactory, linkRegex)
parsed.trimEnd('\n')
} catch (e: Exception) {
Log.e(TAG, "Error parsing normalized HTML: ${e.message}")
Expand All @@ -426,7 +428,7 @@ class EnrichedTextInputView :

if (isInternalHtml) {
try {
val parsed = EnrichedParser.fromHtml(text.toString(), htmlStyle, spannableFactory)
val parsed = EnrichedParser.fromHtml(text.toString(), htmlStyle, spannableFactory, linkRegex)
return parsed.trimEnd('\n')
} catch (e: Exception) {
Log.e(TAG, "Error parsing HTML: ${e.message}")
Expand Down
18 changes: 9 additions & 9 deletions ios/EnrichedTextInputView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,15 @@ - (void)updateProps:(Props::Shared const &)props
textShortcuts = shortcuts;
}

// linkRegex
LinkRegexConfig *oldRegexConfig =
[[LinkRegexConfig alloc] initWithLinkRegexProp:oldViewProps.linkRegex];
LinkRegexConfig *newRegexConfig =
[[LinkRegexConfig alloc] initWithLinkRegexProp:newViewProps.linkRegex];
if (![newRegexConfig isEqualToConfig:oldRegexConfig]) {
[config setLinkRegexConfig:newRegexConfig];
}

// default value - must be set before placeholder to make sure it correctly
// shows on first mount
if (newViewProps.defaultValue != oldViewProps.defaultValue) {
Expand Down Expand Up @@ -775,15 +784,6 @@ - (void)updateProps:(Props::Shared const &)props
[config setMentionIndicators:newIndicators];
}

// linkRegex
LinkRegexConfig *oldRegexConfig =
[[LinkRegexConfig alloc] initWithLinkRegexProp:oldViewProps.linkRegex];
LinkRegexConfig *newRegexConfig =
[[LinkRegexConfig alloc] initWithLinkRegexProp:newViewProps.linkRegex];
if (![newRegexConfig isEqualToConfig:oldRegexConfig]) {
[config setLinkRegexConfig:newRegexConfig];
}

// selection color sets both selection and cursor on iOS (just as in RN)
if (newViewProps.selectionColor != oldViewProps.selectionColor) {
if (isColorMeaningful(newViewProps.selectionColor)) {
Expand Down
5 changes: 4 additions & 1 deletion ios/htmlParser/HtmlParser.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
#import "EnrichedViewHost.h"
#import <UIKit/UIKit.h>

@class EnrichedConfig;

@interface HtmlParser : NSObject
+ (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html
useHtmlNormalizer:(BOOL)useHtmlNormalizer;
+ (NSArray *_Nonnull)getTextAndStylesFromHtml:(NSString *_Nonnull)fixedHtml;
+ (NSArray *_Nonnull)getTextAndStylesFromHtml:(NSString *_Nonnull)fixedHtml
config:(EnrichedConfig *_Nullable)config;
+ (NSString *_Nonnull)parseToHtmlFromRange:(NSRange)range
host:(id<EnrichedViewHost>)host;
@end
9 changes: 7 additions & 2 deletions ios/htmlParser/HtmlParser.mm
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#import "HtmlParser.h"
#import "AlignmentEntry.h"
#import "AlignmentUtils.h"
#import "EnrichedConfig.h"
#import "ImageData.h"
#import "LinkData.h"
#import "MentionParams.h"
Expand Down Expand Up @@ -414,7 +415,9 @@ + (NSString *_Nullable)initiallyProcessHtml:(NSString *_Nonnull)html
return fixedHtml;
}

+ (NSArray *_Nonnull)getTextAndStylesFromHtml:(NSString *_Nonnull)fixedHtml {
+ (NSArray *_Nonnull)getTextAndStylesFromHtml:(NSString *_Nonnull)fixedHtml
config:
(EnrichedConfig *_Nullable)config {
NSMutableString *plainText = [[NSMutableString alloc] initWithString:@""];
NSMutableDictionary *ongoingTags = [[NSMutableDictionary alloc] init];
NSMutableArray *initiallyProcessedTags = [[NSMutableArray alloc] init];
Expand Down Expand Up @@ -747,7 +750,9 @@ + (NSArray *_Nonnull)getTextAndStylesFromHtml:(NSString *_Nonnull)fixedHtml {
LinkData *linkData = [[LinkData alloc] init];
linkData.url = url;
linkData.text = text;
linkData.isManual = ![text isEqualToString:url];
linkData.isManual = !([text isEqualToString:url] &&
[LinkStyle matchesLinkRegexWithConfig:url
config:config]);

stylePair.styleValue = linkData;
} else if ([tagName isEqualToString:@"mention"]) {
Expand Down
9 changes: 6 additions & 3 deletions ios/inputHtmlParser/InputHtmlParser.mm
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ - (void)replaceWholeFromHtml:(NSString *_Nonnull)html {
_input->textView.typingAttributes = _input->defaultTypingAttributes;

@try {
NSArray *processingResult = [HtmlParser getTextAndStylesFromHtml:html];
NSArray *processingResult =
[HtmlParser getTextAndStylesFromHtml:html config:_input.config];
NSString *plainText = (NSString *)processingResult[0];
NSArray *stylesInfo = (NSArray *)processingResult[1];
NSArray *alignments = (NSArray *)processingResult[2];
Expand All @@ -50,7 +51,8 @@ - (void)replaceWholeFromHtml:(NSString *_Nonnull)html {

- (void)replaceFromHtml:(NSString *_Nonnull)html range:(NSRange)range {
@try {
NSArray *processingResult = [HtmlParser getTextAndStylesFromHtml:html];
NSArray *processingResult =
[HtmlParser getTextAndStylesFromHtml:html config:_input.config];
NSString *plainText = (NSString *)processingResult[0];
NSArray *stylesInfo = (NSArray *)processingResult[1];
NSArray *alignments = (NSArray *)processingResult[2];
Expand Down Expand Up @@ -81,7 +83,8 @@ - (void)replaceFromHtml:(NSString *_Nonnull)html range:(NSRange)range {

- (void)insertFromHtml:(NSString *_Nonnull)html location:(NSInteger)location {
@try {
NSArray *processingResult = [HtmlParser getTextAndStylesFromHtml:html];
NSArray *processingResult =
[HtmlParser getTextAndStylesFromHtml:html config:_input.config];
NSString *plainText = (NSString *)processingResult[0];
NSArray *stylesInfo = (NSArray *)processingResult[1];
NSArray *alignments = (NSArray *)processingResult[2];
Expand Down
4 changes: 4 additions & 0 deletions ios/interfaces/StyleHeaders.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#import "MentionParams.h"
#import "StyleBase.h"

@class EnrichedConfig;

@interface BoldStyle : StyleBase
@end

Expand All @@ -28,6 +30,8 @@
- (void)handleAutomaticLinks:(NSString *)word inRange:(NSRange)wordRange;
- (void)handleManualLinks:(NSString *)word inRange:(NSRange)wordRange;
- (void)applyLinkMetaWithData:(LinkData *)linkData range:(NSRange)range;
+ (BOOL)matchesLinkRegexWithConfig:(NSString *)url
config:(EnrichedConfig *)config;
@end

@interface MentionStyle : StyleBase
Expand Down
Loading
Loading