Skip to content

Commit 40f41d2

Browse files
committed
Fix Linking.canOpenURL issue [Closes #16]
1 parent 70f7a55 commit 40f41d2

File tree

4 files changed

+80
-46
lines changed

4 files changed

+80
-46
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class MyComponent extends Component {
4242
| `stripPrefix` | `Boolean` | `true` | Enable stripping of protocol from link text (`https://url` -> `url`). |
4343
| `linkStyle` | `TextStyle` | | Custom styling to apply to Text nodes of links. |
4444
| `onPress` | `function` | | Custom function handler for link press events. Arguments: `link:String`, `match:Object`. *See [Autolinker.js match object](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker.match.Match) for more information about the match object.* |
45-
| `renderLink` | `function` | | Custom render function for rendering link nodes. Arguments: `text:String`, `link:String`, `match:Object`. *See [Autolinker.js match object](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker.match.Match) for more information about the match object.* |
45+
| `renderLink` | `function` | | Custom render function for rendering link nodes. Arguments: `text:String`, `match:Object`, `index:Number`. *See [Autolinker.js match object](http://gregjacobs.github.io/Autolinker.js/docs/#!/api/Autolinker.match.Match) for more information about the match object.* |
4646
| `showAlert` | `Boolean` | `false` | Displays an alert before leaving the app to help with accidental clicks. Possible values: `true`, `false` |
4747
| `truncate` | `Number` | `32` | Truncate long link text for display (e.g. `https://www.google.com/../something.html`). Possible values: `0` to disable, `1+` to truncate to that maximum length. |
4848
| `truncateChars` | `String` | `..` | Characters to replace truncated url segments with, if enabled. |

src/index.js

Lines changed: 42 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,43 +17,68 @@ const styles = StyleSheet.create({
1717
});
1818

1919
export default class Autolink extends Component {
20-
onPress(url, match) {
20+
onPress(match, alertShown) {
21+
// Check if alert needs to be shown
22+
if (this.props.showAlert && !alertShown) {
23+
Alert.alert(
24+
'Leaving App',
25+
'Do you want to continue?',
26+
[
27+
{ text: 'Cancel', style: 'cancel' },
28+
{ text: 'OK', onPress: () => this.onPress(match, true) },
29+
],
30+
);
31+
32+
return;
33+
}
34+
35+
// Get url(s) for match
36+
const [
37+
url,
38+
fallback,
39+
] = this.getUrl(match);
40+
41+
// Call custom onPress handler or open link/fallback
2142
if (this.props.onPress) {
2243
this.props.onPress(url, match);
44+
} else if (this.props.webFallback) {
45+
Linking.canOpenURL(url).then((supported) => {
46+
Linking.openURL(!supported && fallback ? fallback : url);
47+
});
2348
} else {
2449
Linking.openURL(url);
2550
}
2651
}
2752

28-
getURL(match) {
53+
getUrl(match) {
2954
const type = match.getType();
3055

3156
switch (type) {
3257
case 'email': {
33-
return `mailto:${encodeURIComponent(match.getEmail())}`;
58+
return [`mailto:${encodeURIComponent(match.getEmail())}`];
3459
}
3560
case 'hashtag': {
3661
const tag = encodeURIComponent(match.getHashtag());
3762

3863
switch (this.props.hashtag) {
3964
case 'instagram':
40-
return this.selectURL(`instagram://tag?name=${tag}`, `https://www.instagram.com/explore/tags/${tag}/`);
65+
return [`instagram://tag?name=${tag}`, `https://www.instagram.com/explore/tags/${tag}/`];
4166
case 'twitter':
42-
return this.selectURL(`twitter://search?query=%23${tag}`, `https://twitter.com/hashtag/${tag}`);
67+
return [`twitter://search?query=%23${tag}`, `https://twitter.com/hashtag/${tag}`];
4368
default:
44-
return match.getMatchedText();
69+
return [match.getMatchedText()];
4570
}
4671
}
4772
case 'mention': {
4873
const mention = match.getMention();
4974

5075
switch (this.props.mention) {
5176
case 'instagram':
52-
return this.selectURL(`instagram://user?username=${mention}`, `https://www.instagram.com/${mention}/`);
77+
return [`instagram://user?username=${mention}`, `https://www.instagram.com/${mention}/`];
5378
case 'twitter':
54-
return this.selectURL(`twitter://user?screen_name=${mention}`, `https://twitter.com/${mention}`);
79+
return [`twitter://user?screen_name=${mention}`, `https://twitter.com/${mention}`];
5580
default:
56-
return match.getMatchedText();
81+
return [match.getMatchedText()];
5782
}
5883
}
5984
case 'phone': {
@@ -62,44 +87,21 @@ export default class Autolink extends Component {
6287
switch (this.props.phone) {
6388
case 'sms':
6489
case 'text':
65-
return `sms:${number}`;
90+
return [`sms:${number}`];
6691
default:
67-
return `tel:${number}`;
92+
return [`tel:${number}`];
6893
}
6994
}
7095
case 'url': {
71-
return match.getAnchorHref();
96+
return [match.getAnchorHref()];
7297
}
7398
default: {
74-
return match.getMatchedText();
99+
return [match.getMatchedText()];
75100
}
76101
}
77102
}
78103

79-
selectURL(url, fallback) {
80-
if (this.props.webFallback) {
81-
return Linking.canOpenURL(url) ? url : fallback;
82-
}
83-
84-
return url;
85-
}
86-
87-
handlePress(url, match) {
88-
if (this.props.showAlert) {
89-
Alert.alert(
90-
'Leaving App',
91-
'Do you want to continue?',
92-
[
93-
{ text: 'Cancel', style: 'cancel' },
94-
{ text: 'OK', onPress: () => this.onPress(url, match) },
95-
],
96-
);
97-
} else {
98-
this.onPress(url, match);
99-
}
100-
}
101-
102-
renderLink(text, url, match, index) {
104+
renderLink(text, match, index) {
103105
const truncated = (this.props.truncate > 0) ?
104106
Autolinker.truncate.TruncateSmart(text, this.props.truncate, this.props.truncateChars) :
105107
text;
@@ -108,7 +110,7 @@ export default class Autolink extends Component {
108110
<Text
109111
key={index}
110112
style={[styles.link, this.props.linkStyle]}
111-
onPress={() => this.handlePress(url, match)}
113+
onPress={() => this.onPress(match)}
112114
>
113115
{truncated}
114116
</Text>
@@ -192,8 +194,8 @@ export default class Autolink extends Component {
192194
case 'phone':
193195
case 'url':
194196
return (renderLink) ?
195-
renderLink(match.getAnchorText(), this.getURL(match), match, index) :
196-
this.renderLink(match.getAnchorText(), this.getURL(match), match, index);
197+
renderLink(match.getAnchorText(), match, index) :
198+
this.renderLink(match.getAnchorText(), match, index);
197199
default:
198200
return part;
199201
}

test/.eslintrc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,14 @@
22
"env": {
33
"node": true,
44
"mocha": true
5+
},
6+
"rules": {
7+
"comma-dangle": ["error", {
8+
arrays: "always-multiline",
9+
objects: "always-multiline",
10+
imports: "always-multiline",
11+
exports: "always-multiline",
12+
functions: "ignore"
13+
}]
514
}
615
}

test/test.js

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,16 @@ describe('<Autolink />', () => {
8080
});
8181

8282
it('should link multiple elements individually', () => {
83-
const wrapper = shallow(<Autolink text="Hi @josh ([email protected] or 415-555-5555), check out https://github.com/joshswan/react-native-autolink. It's #awesome!" email hashtag="instagram" mention="twitter" phone url />);
83+
const wrapper = shallow(
84+
<Autolink
85+
text="Hi @josh ([email protected] or 415-555-5555), check out https://github.com/joshswan/react-native-autolink. It's #awesome!"
86+
email
87+
hashtag="instagram"
88+
mention="twitter"
89+
phone
90+
url
91+
/>
92+
);
8493
expect(wrapper.find('Text')).to.have.length(6);
8594
});
8695

@@ -91,24 +100,38 @@ describe('<Autolink />', () => {
91100
});
92101

93102
it('should truncate urls to length specified in truncate prop', () => {
94-
const wrapper = shallow(<Autolink text="github.com/joshswan/react-native-autolink" truncate={32} />);
103+
const wrapper = shallow(
104+
<Autolink
105+
text="github.com/joshswan/react-native-autolink"
106+
truncate={32}
107+
/>
108+
);
95109
expect(wrapper.contains('github.com/joshswan/react-native-autolink')).to.equal(false);
96110
expect(wrapper.contains('github.com/joshswan/..e-autolink')).to.equal(true);
97111
});
98112

99113
it('should not truncate urls when zero is passed for truncate prop', () => {
100-
const wrapper = shallow(<Autolink text="github.com/joshswan/react-native-autolink" truncate={0} />);
114+
const wrapper = shallow(
115+
<Autolink
116+
text="github.com/joshswan/react-native-autolink"
117+
truncate={0}
118+
/>
119+
);
101120
expect(wrapper.contains('github.com/joshswan/react-native-autolink')).to.equal(true);
102121
expect(wrapper.contains('github.com/joshswan/..e-autolink')).to.equal(false);
103122
});
104123

105124
it('should replace removed protion of truncated url with truncateChars prop value', () => {
106-
const wrapper = shallow(<Autolink text="github.com/joshswan/react-native-autolink" truncate={32} truncateChars="__" />);
125+
const wrapper = shallow(<Autolink
126+
text="github.com/joshswan/react-native-autolink"
127+
truncate={32}
128+
truncateChars="__"
129+
/>);
107130
expect(wrapper.contains('github.com/joshswan/__e-autolink')).to.equal(true);
108131
});
109132

110133
it('should use function to render links if passed using renderLink prop', () => {
111-
const renderLink = (text, url, match, index) => <Text>{`${text}:${index}`}</Text>;
134+
const renderLink = (text, match, index) => <Text>{`${text}:${index}`}</Text>;
112135
const wrapper = shallow(<Autolink text="[email protected]" renderLink={renderLink} />);
113136
expect(wrapper.contains(<Text>[email protected]:0</Text>)).to.equal(true);
114137
});

0 commit comments

Comments
 (0)