From 69ea4b9b5a557daddf140cc4d3372692294e02cc Mon Sep 17 00:00:00 2001 From: Doron Tohar Date: Sun, 13 Aug 2017 12:23:53 +0300 Subject: [PATCH] Add the component --- HyperlinkedText.js | 113 +++++++++++++++++++++++++++++++++ Styles/HyperlinkedTextStyle.js | 12 ++++ package.json | 26 ++++++++ yarn.lock | 7 ++ 4 files changed, 158 insertions(+) create mode 100644 HyperlinkedText.js create mode 100644 Styles/HyperlinkedTextStyle.js create mode 100644 package.json create mode 100644 yarn.lock diff --git a/HyperlinkedText.js b/HyperlinkedText.js new file mode 100644 index 0000000..24847b3 --- /dev/null +++ b/HyperlinkedText.js @@ -0,0 +1,113 @@ +/** + * @providesModule react-native-hyperlinked-text + */ + +import React, { Component, PropTypes } from 'react' +import { + View, + Text, + Linking, +} from 'react-native' +import styles from './Styles/HyperlinkedTextStyle'; +import R from 'ramda'; + +'use strict'; + +const textPropTypes = Text.propTypes || {} + +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 ({ newElements }); + } + + _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? ()=>{} : (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( + onPress(match.text, ...match.groups))} + > + {linkDef.replaceText?linkDef.replaceText(match.text, ...match.groups):match.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 +} \ No newline at end of file diff --git a/Styles/HyperlinkedTextStyle.js b/Styles/HyperlinkedTextStyle.js new file mode 100644 index 0000000..219db59 --- /dev/null +++ b/Styles/HyperlinkedTextStyle.js @@ -0,0 +1,12 @@ +import { StyleSheet } from 'react-native' +import { Metrics, Colors } from '../../Themes/' + +export default StyleSheet.create({ + container: { + flex: 1, + paddingTop: Metrics.titlePadding + }, + hyperlink: { + color: Colors.hyperlink + } +}) diff --git a/package.json b/package.json new file mode 100644 index 0000000..54b3927 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "react-native-hyperlinked-text", + "version": "0.0.1", + "keywords": [ + "react-native" + ], + "dependencies": { + "ramda": "^0.24.1" + }, + "description": "Text component for React Native with regex defined hyperlinks", + "main": "HyperlinkedText.js", + "devDependencies": {}, + "scripts": { + "test": "mocha --reporter spec" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/Kimaia/react-native-hyperlinked-text.git" + }, + "author": "Doron Tohar (https://www.kimaia.com/)", + "license": "ISC", + "bugs": { + "url": "https://github.com/Kimaia/react-native-hyperlinked-text/issues" + }, + "homepage": "https://github.com/Kimaia/react-native-hyperlinked-text#readme" +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..fe19662 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,7 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +ramda@^0.24.1: + version "0.24.1" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.24.1.tgz#c3b7755197f35b8dc3502228262c4c91ddb6b857"