diff --git a/README.md b/README.md
index 1dec11c..a2dbc3e 100644
--- a/README.md
+++ b/README.md
@@ -32,9 +32,9 @@ If you're feeling lazy, you can wrap `Linkify` around anywhere that you want lin
Renders to:
-react-linkify (`tasti.github.io/react-linkify/`)
-React component to parse links (urls, emails, etc.) in text into clickable links
-See examples at `tasti.github.io/react-linkify/`.
+react-linkify (`tasti.github.io/react-linkify/`)
+React component to parse links (urls, emails, etc.) in text into clickable links
+See examples at `tasti.github.io/react-linkify/`.
Contact: `tasti@zakarie.com`
@@ -57,18 +57,38 @@ React.render(
## Props
-**component**
-The type of component to wrap links in.
-_type:_ `any`
-_default:_ `'a'`
+**component**
+The type of component to wrap links in.
+_type:_ `any`
+_default:_ `'a'`
-**properties**
-The props that will be added to every matched component.
-_type:_ `object`
+**properties**
+The props that will be added to every matched component.
+_type:_ `object`
_default:_ `{}`
NOTE: Use `Linkify.MATCH` as a value to specify the matched link. The properties prop will always contain `{href: Linkify.MATCH, key: 'LINKIFY_KEY_#'}` unless overridden.
+**handlers**
+Handlers to match custom link types, like Twitter @mentions.
+
+_type_:
+```js
+arrayOf(
+ shape({
+ prefix: string.isRequired,
+ validate: func.isRequired,
+ normalize: func.isRequired
+ })
+)
+```
+_default:_ `[]`
+
+See the [example mentions handler](https://github.com/markdown-it/linkify-it#example-2-add-twitter-mentions-handler) from linkify-it for more details.
+
+## Customization
+Custom handlers can be added to a specific `Linkify` instance with the `handlers` prop. Additionally, the singleton linkify-it instance can be imported (`import { linkify } from 'react-linkify'`) for global customization. See the [linkify-it api docs](http://markdown-it.github.io/linkify-it/doc/)
+
## Examples
All kind of links detectable by
diff --git a/src/Linkify.jsx b/src/Linkify.jsx
index 8f86702..6ed4a9f 100644
--- a/src/Linkify.jsx
+++ b/src/Linkify.jsx
@@ -2,8 +2,33 @@ import React from 'react';
import LinkifyIt from 'linkify-it';
import tlds from 'tlds';
-const linkify = new LinkifyIt();
-linkify.tlds(tlds);
+const globalCustomizations = {
+ add: [],
+ tlds: [],
+ set: []
+};
+
+export const config = {
+ add: (...args) => {
+ globalCustomizations.add.push(args);
+ return config;
+ },
+ tlds: (...args) => {
+ globalCustomizations.tlds.push(args);
+ return config;
+ },
+ set: (...args) => {
+ globalCustomizations.set.push(args);
+ return config;
+ },
+ resetAll: () => {
+ for (let type in globalCustomizations) {
+ globalCustomizations[type] = [];
+ }
+
+ return config;
+ }
+};
class Linkify extends React.Component {
static MATCH = 'LINKIFY_MATCH'
@@ -13,19 +38,62 @@ class Linkify extends React.Component {
component: React.PropTypes.any,
properties: React.PropTypes.object,
urlRegex: React.PropTypes.object,
- emailRegex: React.PropTypes.object
+ emailRegex: React.PropTypes.object,
+ handlers: React.PropTypes.arrayOf(
+ React.PropTypes.shape({
+ prefix: React.PropTypes.string.isRequired,
+ validate: React.PropTypes.func.isRequired,
+ normalize: React.PropTypes.func.isRequired
+ })
+ ),
+ fuzzyLink: React.PropTypes.bool,
+ fuzzyIP: React.PropTypes.bool,
+ fuzzyEmail: React.PropTypes.bool
}
static defaultProps = {
className: 'Linkify',
component: 'a',
properties: {},
+ handlers: []
+ }
+
+ componentDidMount() {
+ this.addCustomHandlers();
+ }
+
+ componentDidUpdate(nextProps) {
+ this.addCustomHandlers();
+ }
+
+ addCustomHandlers() {
+ const { handlers } = this.props;
+
+ this.linkify = this.linkify || new LinkifyIt();
+ this.linkify.tlds(tlds);
+
+ // add global customizations
+ for (let type in globalCustomizations) {
+ globalCustomizations[type].forEach(c => this.linkify[type](...c))
+ }
+
+ // add instance customizations
+ (handlers || []).forEach((handler) => {
+ this.linkify.add(handler.prefix, {
+ validate: handler.validate,
+ normalize: handler.normalize
+ });
+ });
+
+ ['fuzzyLink', 'fuzzyIP', 'fuzzyEmail'].forEach(f => {
+ typeof this.props[f] === 'boolean' && this.linkify.set({ [f]: this.props[f] })
+ })
}
parseCounter = 0
getMatches(string) {
- return linkify.match(string);
+ return this.linkify.match(string);
}
parseString(string) {
diff --git a/src/__tests__/Linkify-test.js b/src/__tests__/Linkify-test.js
index 196384c..528a986 100644
--- a/src/__tests__/Linkify-test.js
+++ b/src/__tests__/Linkify-test.js
@@ -5,6 +5,8 @@ let TestUtils = require('react-addons-test-utils');
describe('Linkify', () => {
let Linkify = require('../Linkify.jsx').default;
+ let linkifyCustomizations = require('../Linkify.jsx').config;
+
describe('#parseString', () => {
let linkify = TestUtils.renderIntoDocument();
@@ -121,6 +123,174 @@ describe('Linkify', () => {
});
});
+ describe('LinkifyIt config', () => {
+ it('should match a custom handler added through the "handlers" prop', () => {
+ const linkify = TestUtils.renderIntoDocument(
+
+
+ );
+
+ const input = ['this is an ', '@mention', ' handler'];
+ const output = linkify.parseString(input.join(''));
+
+ expect(output[0]).toEqual(input[0]);
+ expect(output[1].type).toEqual('a');
+ expect(output[1].props.href).toEqual(`https://twitter.com/mention`);
+ expect(output[1].props.children).toEqual(input[1]);
+ expect(output[2]).toEqual(input[2]);
+ });
+
+ it('should match multiple custom handlers', () => {
+ const linkify = TestUtils.renderIntoDocument(
+
+
+ );
+
+ const input = ['this is an ', '@mention', ' and ', '$mention', ' handler'];
+ const output = linkify.parseString(input.join(''));
+
+ expect(output[0]).toEqual(input[0]);
+ expect(output[1].type).toEqual('a');
+ expect(output[1].props.href).toEqual(`https://twitter.com/mention`);
+ expect(output[1].props.children).toEqual(input[1]);
+
+ expect(output[2]).toEqual(input[2]);
+ expect(output[3].type).toEqual('a');
+ expect(output[3].props.href).toEqual(`https://blingtwitter.com/mention`);
+ expect(output[3].props.children).toEqual(input[3]);
+ expect(output[4]).toEqual(input[4]);
+ })
+
+ it('should apply global customizations', () => {
+ linkifyCustomizations
+ .resetAll()
+ .tlds('linkify', true)
+ .add('@', {
+ validate() {
+ return 7;
+ },
+ normalize(match) {
+ match.url = 'https://twitter.com/' + match.url.replace(/^@/, '');
+ }
+ })
+ const linkify = TestUtils.renderIntoDocument(
+
+ );
+
+ const input = ['this is an ', '@mention', ' and ', 'test.linkify', ' TLD handler'];
+ const output = linkify.parseString(input.join(''));
+
+ expect(output[0]).toEqual(input[0]);
+ expect(output[1].type).toEqual('a');
+ expect(output[1].props.href).toEqual(`https://twitter.com/mention`);
+ expect(output[1].props.children).toEqual(input[1]);
+
+ expect(output[2]).toEqual(input[2]);
+ expect(output[3].type).toEqual('a');
+ expect(output[3].props.href).toEqual(`http://test.linkify`);
+ expect(output[3].props.children).toEqual(input[3]);
+ expect(output[4]).toEqual(input[4]);
+ });
+
+ it('should merge global and instance handlers', () => {
+ linkifyCustomizations
+ .resetAll()
+ .add('@', {
+ validate() {
+ return 7;
+ },
+ normalize(match) {
+ match.url = 'https://twitter.com/' + match.url.replace(/^@/, '');
+ }
+ })
+ const linkify = TestUtils.renderIntoDocument(
+
+
+ );
+
+ const input = ['this is an ', '@mention', ' and ', '$mention', ' handler'];
+ const output = linkify.parseString(input.join(''));
+
+ expect(output[0]).toEqual(input[0]);
+ expect(output[1].type).toEqual('a');
+ expect(output[1].props.href).toEqual(`https://twitter.com/mention`);
+ expect(output[1].props.children).toEqual(input[1]);
+
+ expect(output[2]).toEqual(input[2]);
+ expect(output[3].type).toEqual('a');
+ expect(output[3].props.href).toEqual(`https://blingtwitter.com/mention`);
+ expect(output[3].props.children).toEqual(input[3]);
+ expect(output[4]).toEqual(input[4]);
+ });
+
+ it('should set fuzzy* options', () => {
+ const linkify = TestUtils.renderIntoDocument(
+
+ );
+
+ const linkInput = 'this should not match: www.test.com';
+ const linkOutput = linkify.parseString(linkInput);
+ expect(linkOutput).toEqual(linkInput);
+ });
+
+ it('should reset global customizations', () => {
+ linkifyCustomizations
+ .add('@', {
+ validate() {
+ return 7;
+ },
+ normalize(match) {
+ match.url = 'https://twitter.com/' + match.url.replace(/^@/, '');
+ }
+ })
+ .resetAll()
+
+ const linkify = TestUtils.renderIntoDocument(
+
+ );
+
+ const input = 'this @mention should not match';
+ const output = linkify.parseString(input);
+
+ expect(output).toEqual(input);
+ })
+ });
+
describe('#render', () => {
let linkify = TestUtils.renderIntoDocument();