forked from Kimaia/react-native-hyperlinked-text
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathHyperlinkedText.js
140 lines (126 loc) · 3.46 KB
/
HyperlinkedText.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
* @providesModule react-native-hyperlinked-text
*/
import React, { Component } from "react";
import PropTypes from "prop-types";
import { View, Text, Linking } from "react-native";
import styles from "./Styles/HyperlinkedTextStyle";
import R from "ramda";
("use strict");
const textPropTypes = Text.propTypes || {};
/**
* Replaces the string child with a hyperlinked version according to configuration
*/
export default class HyperlinkedText extends Component {
constructor(props) {
super(props);
this._getMatches = this._getMatches.bind(this);
}
render() {
return this._linkify(this);
}
_linkify(component) {
const matches = this._gatherMatches(component.props.children);
const newElements = this._replaceMatches(component, matches);
return (
<Text
allowFontScaling={false}
{...component.props}
style={this.props.style}
>
{newElements}
</Text>
);
}
_gatherMatches(text) {
const matches = this.props.linkDefs.reduce(
(matches, linkDef) => R.concat(this._getMatches(linkDef, text), matches),
[]
);
return R.sort((m1, m2) => m1.index - m2.index, matches);
}
_getMatches(linkDef, text) {
const regex = linkDef.regex;
regex.lastIndex = 0; // reset the regex in case it was used before
let matches = [];
while ((regexResult = regex.exec(text)) !== null) {
matches.push({
text: regexResult[0],
groups: R.drop(1, regexResult),
index: regexResult.index,
lastIndex: regex.lastIndex,
linkDef,
});
}
return matches;
}
_replaceMatches(component, matches) {
const componentProps = {
...component.props,
ref: undefined,
key: undefined,
};
let _lastIndex = 0;
const elements = [];
for (let match of matches) {
const linkDef = match.linkDef;
const style = linkDef.style || this.props.linkStyle;
const onPress = linkDef.noPress
? undefined
: linkDef.onPress || this.props.onLinkPress;
const replaceText = linkDef.replaceText || R.identity;
let nonLinkedText = component.props.children.substring(
_lastIndex,
match.index
);
nonLinkedText && elements.push(nonLinkedText);
_lastIndex = match.lastIndex;
elements.push(
<Text
{...componentProps}
key={match.index}
style={[component.props.style, style]}
onPress={onPress && (() => onPress(match.text, ...match.groups))}
>
{linkDef.replaceText
? linkDef.replaceText(match.text, ...match.groups)
: match.text}
</Text>
);
}
elements.push(
component.props.children.substring(
_lastIndex,
component.props.children.length
)
);
return elements;
}
static _openWebUrl(url) {
Linking.canOpenURL(url)
.then((supported) => supported && Linking.openURL(url))
.catch(console.log("Failed to open url " + url));
}
static get webUrlLinkDef() {
return {
regex: /(https?:\/\/[^\s]+)/gim,
onPress: HyperlinkedText._openWebUrl,
linkStyle: styles.hyperlink,
};
}
}
HyperlinkedText.propTypes = {
onLinkPress: PropTypes.func,
linkDefs: PropTypes.array,
linkStyle: textPropTypes.style,
};
// Defaults for props
HyperlinkedText.defaultProps = {
onLinkPress: HyperlinkedText._openWebUrl,
linkDefs: [
{
regex: /(https?:\/\/[^\s]+)/gim,
},
],
linkStyle: styles.hyperlink,
};