|
@@ -57,13 +57,85 @@ const FixerItem = ({
|
|
|
);
|
|
|
};
|
|
|
|
|
|
- const openURLWithPrefix = async (url: string) => {
|
|
|
- const prefixedURL = url.startsWith('http://') || url.startsWith('https://') ? url : `http://${url}`;
|
|
|
- const supported = await Linking.canOpenURL(prefixedURL);
|
|
|
-
|
|
|
- if (supported) {
|
|
|
- Linking.openURL(prefixedURL);
|
|
|
+ const parseTextWithLinks = (text?: string): React.ReactNode => {
|
|
|
+ if (!text) return null;
|
|
|
+
|
|
|
+ const urlRegex = /((https?:\/\/[^\s]+)|(?<!\w)(www\.[^\s]+))/g;
|
|
|
+
|
|
|
+ const result: React.ReactNode[] = [];
|
|
|
+ let lastIndex = 0;
|
|
|
+
|
|
|
+ const matches = Array.from(text.matchAll(urlRegex));
|
|
|
+
|
|
|
+ matches.forEach((match, index) => {
|
|
|
+ const matchText = match[0];
|
|
|
+ const matchIndex = match.index ?? 0;
|
|
|
+
|
|
|
+ if (matchIndex > lastIndex) {
|
|
|
+ const betweenText = text.slice(lastIndex, matchIndex);
|
|
|
+ result.push(...processNonUrlText(betweenText, `before-${index}`));
|
|
|
+ }
|
|
|
+
|
|
|
+ let url = matchText.startsWith('http') ? matchText : `https://${matchText}`;
|
|
|
+ result.push(
|
|
|
+ <Text
|
|
|
+ key={`link-${index}`}
|
|
|
+ style={styles.linkText}
|
|
|
+ onPress={async () => {
|
|
|
+ const supported = await Linking.canOpenURL(url);
|
|
|
+ if (supported) {
|
|
|
+ Linking.openURL(url);
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ selectable
|
|
|
+ >
|
|
|
+ {matchText}
|
|
|
+ </Text>
|
|
|
+ );
|
|
|
+
|
|
|
+ lastIndex = matchIndex + matchText.length;
|
|
|
+ });
|
|
|
+
|
|
|
+ if (lastIndex < text.length) {
|
|
|
+ const remainingText = text.slice(lastIndex);
|
|
|
+ result.push(...processNonUrlText(remainingText, 'text-end'));
|
|
|
}
|
|
|
+
|
|
|
+ return <Text>{result}</Text>;
|
|
|
+ };
|
|
|
+
|
|
|
+ const processNonUrlText = (text: string, keyPrefix: string): React.ReactNode[] => {
|
|
|
+ const parts = text.split(/(\s+|,|;|\/)/);
|
|
|
+ return parts.map((part, i) => {
|
|
|
+ const trimmed = part.trim();
|
|
|
+ const isSeparator = /^(\s+|,|;|\/)$/.test(part);
|
|
|
+ const shouldBeLink = !isSeparator && trimmed.length > 3;
|
|
|
+
|
|
|
+ if (shouldBeLink) {
|
|
|
+ const url = trimmed.startsWith('http') ? trimmed : `https://${trimmed}`;
|
|
|
+ return (
|
|
|
+ <Text
|
|
|
+ key={`${keyPrefix}-link-${i}`}
|
|
|
+ style={styles.linkText}
|
|
|
+ onPress={async () => {
|
|
|
+ const supported = await Linking.canOpenURL(url);
|
|
|
+ if (supported) {
|
|
|
+ Linking.openURL(url);
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ selectable
|
|
|
+ >
|
|
|
+ {part}
|
|
|
+ </Text>
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <Text key={`${keyPrefix}-text-${i}`} selectable>
|
|
|
+ {part}
|
|
|
+ </Text>
|
|
|
+ );
|
|
|
+ });
|
|
|
};
|
|
|
|
|
|
return (
|
|
@@ -123,9 +195,7 @@ const FixerItem = ({
|
|
|
{item.web && (
|
|
|
<View style={styles.rowContent}>
|
|
|
<Text style={styles.labelText}>Website:</Text>
|
|
|
- <TouchableOpacity style={{ flex: 4 }} onPress={() => openURLWithPrefix(item.web)}>
|
|
|
- <Text style={[styles.linkText]} selectable>{item.web}</Text>
|
|
|
- </TouchableOpacity>
|
|
|
+ <View style={{ flex: 4 }}>{parseTextWithLinks(item.web)}</View>
|
|
|
</View>
|
|
|
)}
|
|
|
|