-
Notifications
You must be signed in to change notification settings - Fork 12
/
HyperlinkedText.js
117 lines (103 loc) · 3.23 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
/**
* @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 {...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]+)/mgi,
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]+)/mgi
}],
linkStyle: styles.hyperlink
}