Skip to content

Commit

Permalink
Revamped options utility (#154)
Browse files Browse the repository at this point in the history
* Enhancements and deprecations for options hash

Features

* Added ability to override default options via linkify.options.defaults
* Options that take functions with value and type arguments can now be
  specified as objects, where each key is the target type. For example:

```
'github.com is #rad, please email nick@example.com'.linkify({
    formatHref: {
        email: (mailto) => `${mailto}?subject=Hello`,
        hashtag: (hashtag) => `https://twitter.com/hashtag/${hashtag.substr(1)}`
    }
});
```

Bug fixes

* Allow disabling linkClass (by using default options) - Fixes #144

Deprecations

* Deprecated `linkClass` option. Use `className` instead
* Deprecated `linkAttributes` option. Use `attributes` instead.
* The `linkified` default class name for links will no longer be provided in a
  future release

Breaking Changes

* Fully removed deprecated `newLine` option

* Unit tests for linkify options utilities

Including improvements and fixes for QUnit tests

* Additional refactorings and updates to get IE to work

Note to self: IE8 is the devil
  • Loading branch information
nfrasser committed Aug 16, 2016
1 parent d659054 commit 2185b8a
Show file tree
Hide file tree
Showing 17 changed files with 382 additions and 239 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -144,7 +144,7 @@ require(['linkify-element'], function (linkifyElement) {

// Linkify the #sidebar element
linkifyElement(document.getElementById('sidebar'), {
linkClass: 'my-link'
className: 'my-link'
});

// Linkify all paragraph tags
Expand Down
48 changes: 25 additions & 23 deletions src/linkify-element.js
Expand Up @@ -3,8 +3,10 @@
*/

import * as linkify from './linkify';
let tokenize = linkify.tokenize;
let options = linkify.options;

const {tokenize, options} = linkify;
const {Options} = options;

let TEXT_TOKEN = linkify.parser.TOKENS.TEXT;

const HTML_NODE = 1, TXT_NODE = 3;
Expand Down Expand Up @@ -33,41 +35,41 @@ function replaceChildWithChildren(parent, oldChild, newChildren) {
function tokensToNodes(tokens, opts, doc) {
let result = [];

for (let i = 0; i < tokens.length; i++) {
let token = tokens[i];

for (const token of tokens) {
if (token.type === 'nl' && opts.nl2br) {
result.push(doc.createElement('br'));
continue;
} else if (
!token.isLink ||
!options.resolve(opts.validate, token.toString(), token.type)
) {
} else if (!token.isLink || !opts.check(token)) {
result.push(doc.createTextNode(token.toString()));
continue;
}

let href = token.toHref(opts.defaultProtocol);
let formatted = options.resolve(opts.format, token.toString(), token.type);
let formattedHref = options.resolve(opts.formatHref, href, token.type);
let attributesHash = options.resolve(opts.attributes, href, token.type);
let tagName = options.resolve(opts.tagName, href, token.type);
let linkClass = options.resolve(opts.linkClass, href, token.type);
let target = options.resolve(opts.target, href, token.type);
let events = options.resolve(opts.events, href, token.type);
let {
formatted,
formattedHref,
tagName,
className,
target,
events,
attributes,
} = opts.resolve(token);

// Build the link
let link = doc.createElement(tagName);
link.setAttribute('href', formattedHref);
link.setAttribute('class', linkClass);

if (className) {
link.setAttribute('class', className);
}

if (target) {
link.setAttribute('target', target);
}

// Build up additional attributes
if (attributesHash) {
for (var attr in attributesHash) {
link.setAttribute(attr, attributesHash[attr]);
if (attributes) {
for (var attr in attributes) {
link.setAttribute(attr, attributes[attr]);
}
}

Expand Down Expand Up @@ -153,12 +155,12 @@ function linkifyElement(element, opts, doc = false) {
);
}

opts = options.normalize(opts);
opts = new Options(opts);
return linkifyElementHelper(element, opts, doc);
}

// Maintain reference to the recursive helper to cache option-normalization
linkifyElement.helper = linkifyElementHelper;
linkifyElement.normalize = options.normalize;
linkifyElement.normalize = (opts) => new Options(opts);

export default linkifyElement;
41 changes: 23 additions & 18 deletions src/linkify-html.js
@@ -1,7 +1,9 @@
import HTML5Tokenizer from './simple-html-tokenizer';
import * as linkify from './linkify';

const options = linkify.options;
const {options} = linkify;
const {Options} = options;

const StartTag = 'StartTag';
const EndTag = 'EndTag';
const Chars = 'Chars';
Expand All @@ -17,7 +19,7 @@ export default function linkifyHtml(str, opts={}) {
let linkified = [];
var i;

opts = linkify.options.normalize(opts);
opts = new Options(opts);

// Linkify the tokens given by the parser
for (i = 0; i < tokens.length; i++) {
Expand Down Expand Up @@ -86,8 +88,6 @@ function linkifyChars(str, opts) {

for (var i = 0; i < tokens.length; i++) {
let token = tokens[i];
let validated = token.isLink
&& linkify.options.resolve(opts.validate, token.toString(), token.type);

if (token.type === 'nl' && opts.nl2br) {
result.push({
Expand All @@ -97,38 +97,43 @@ function linkifyChars(str, opts) {
selfClosing: true
});
continue;
} else if (!token.isLink || !validated) {
} else if (!token.isLink || !opts.check(token)) {
result.push({type: Chars, chars: token.toString()});
continue;
}

let href = token.toHref(opts.defaultProtocol);
let formatted = linkify.options.resolve(opts.format, token.toString(), token.type);
let formattedHref = linkify.options.resolve(opts.formatHref, href, token.type);
let attributesHash = linkify.options.resolve(opts.attributes, href, token.type);
let tagName = linkify.options.resolve(opts.tagName, href, token.type);
let linkClass = linkify.options.resolve(opts.linkClass, href, token.type);
let target = linkify.options.resolve(opts.target, href, token.type);
let {
href,
formatted,
formattedHref,
tagName,
className,
target,
attributes
} = opts.resolve(token);

// Build up attributes
let attributes = [
let attributeArray = [
['href', formattedHref],
['class', linkClass]
];

if (className) {
attributeArray.push(['class', className]);
}

if (target) {
attributes.push(['target', target]);
attributeArray.push(['target', target]);
}

for (var attr in attributesHash) {
attributes.push([attr, attributesHash[attr]]);
for (var attr in attributes) {
attributeArray.push([attr, attributes[attr]]);
}

// Add the required tokens
result.push({
type: StartTag,
tagName: tagName,
attributes: attributes,
attributes: attributeArray,
selfClosing: false
});
result.push({type: Chars, chars: formatted});
Expand Down
23 changes: 11 additions & 12 deletions src/linkify-jquery.js
Expand Up @@ -39,18 +39,17 @@ export default function apply($, doc = false) {
let target = data.linkify;
let nl2br = data.linkifyNlbr;
let options = {
linkAttributes: data.linkifyAttributes,
defaultProtocol: data.linkifyDefaultProtocol,
events: data.linkifyEvents,
format: data.linkifyFormat,
formatHref: data.linkifyFormatHref,
newLine: data.linkifyNewline, // deprecated
nl2br: !!nl2br && nl2br !== 0 && nl2br !== 'false',
tagName: data.linkifyTagname,
target: data.linkifyTarget,
linkClass: data.linkifyLinkclass,
validate: data.linkifyValidate,
ignoreTags: data.linkifyIgnoreTags
attributes: data.linkifyAttributes,
defaultProtocol: data.linkifyDefaultProtocol,
events: data.linkifyEvents,
format: data.linkifyFormat,
formatHref: data.linkifyFormatHref,
nl2br: !!nl2br && nl2br !== 0 && nl2br !== 'false',
tagName: data.linkifyTagname,
target: data.linkifyTarget,
className: data.linkifyClassName || data.linkifyLinkclass, // linkClass is deprecated
validate: data.linkifyValidate,
ignoreTags: data.linkifyIgnoreTags
};
let $target = target === 'this' ? $this : $this.find(target);
$target.linkify(options);
Expand Down
47 changes: 26 additions & 21 deletions src/linkify-react.js
@@ -1,7 +1,8 @@
import React from 'react';
import * as linkify from './linkify';

let options = linkify.options;
const {options} = linkify;
const {Options} = options;

// Given a string, converts to an array of valid React components
// (which may include strings)
Expand All @@ -11,43 +12,47 @@ function stringToElements(str, opts) {
let elements = [];
var linkId = 0;

for (const token of tokens) {
for (var i = 0; i < tokens.length; i++) {
let token = tokens[i];

if (token.type === 'nl' && opts.nl2br) {
elements.push(React.createElement('br', {key: `linkified-${++linkId}`}));
continue;
} else if (
!token.isLink ||
!options.resolve(opts.validate, token.toString(), token.type)
) {
} else if (!token.isLink || !opts.check(token)) {
// Regular text
elements.push(token.toString());
continue;
}

let href = token.toHref(opts.defaultProtocol);
let formatted = options.resolve(opts.format, token.toString(), token.type);
let formattedHref = options.resolve(opts.formatHref, href, token.type);
let attributesHash = options.resolve(opts.attributes, href, token.type);
let tagName = options.resolve(opts.tagName, href, token.type);
let linkClass = options.resolve(opts.linkClass, href, token.type);
let target = options.resolve(opts.target, href, token.type);
let events = options.resolve(opts.events, href, token.type);
let {
href,
formatted,
formattedHref,
tagName,
className,
target,
attributes,
events
} = opts.resolve(token);

let props = {
key: `linkified-${++linkId}`,
href: href,
className: linkClass,
href: formattedHref,
};

if (className) {
props.className = className;
}

if (target) {
props.target = target;
}

// Build up additional attributes
// Support for events via attributes hash
if (attributesHash) {
for (var attr in attributesHash) {
props[attr] = attributesHash[attr];
if (attributes) {
for (var attr in attributes) {
props[attr] = attributes[attr];
}
}

Expand Down Expand Up @@ -103,8 +108,8 @@ var Linkify = React.createClass({
}
}

var opts = options.normalize(this.props.options);
var tagName = this.props.tagName || 'span';
let opts = new Options(this.props.options);
let tagName = this.props.tagName || 'span';
let element = React.createElement(tagName, newProps);

return linkifyReactElement(element, opts, 0);
Expand Down

0 comments on commit 2185b8a

Please sign in to comment.