Skip to content

Commit 0aa5eec

Browse files
author
Nick Frasser
authored
Linkify element fixes (#148)
* Small refactoring and optimizations for linkify-element * Added test for empty text node on linkify-element * Additional small tweaks and enhancements * For jquery test and linkify-string Fixes #145
1 parent a172980 commit 0aa5eec

File tree

4 files changed

+64
-100
lines changed

4 files changed

+64
-100
lines changed

src/linkify-element.js

Lines changed: 55 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
*/
44

55
import * as linkify from './linkify';
6-
var tokenize = linkify.tokenize;
7-
var options = linkify.options;
6+
let tokenize = linkify.tokenize;
7+
let options = linkify.options;
8+
let TEXT_TOKEN = linkify.parser.TOKENS.TEXT;
89

910
const HTML_NODE = 1, TXT_NODE = 3;
1011

@@ -34,53 +35,54 @@ function tokensToNodes(tokens, opts, doc) {
3435

3536
for (let i = 0; i < tokens.length; i++) {
3637
let token = tokens[i];
37-
let validated = token.isLink && options.resolve(opts.validate, token.toString(), token.type);
38-
39-
if (token.isLink && validated) {
40-
41-
let
42-
href = token.toHref(opts.defaultProtocol),
43-
formatted = options.resolve(opts.format, token.toString(), token.type),
44-
formattedHref = options.resolve(opts.formatHref, href, token.type),
45-
attributesHash = options.resolve(opts.attributes, href, token.type),
46-
tagName = options.resolve(opts.tagName, href, token.type),
47-
linkClass = options.resolve(opts.linkClass, href, token.type),
48-
target = options.resolve(opts.target, href, token.type),
49-
events = options.resolve(opts.events, href, token.type);
50-
51-
// Build the link
52-
let link = doc.createElement(tagName);
53-
link.setAttribute('href', formattedHref);
54-
link.setAttribute('class', linkClass);
55-
if (target) {
56-
link.setAttribute('target', target);
57-
}
5838

59-
// Build up additional attributes
60-
if (attributesHash) {
61-
for (var attr in attributesHash) {
62-
link.setAttribute(attr, attributesHash[attr]);
63-
}
39+
if (token.type === 'nl' && opts.nl2br) {
40+
result.push(doc.createElement('br'));
41+
continue;
42+
} else if (
43+
!token.isLink ||
44+
!options.resolve(opts.validate, token.toString(), token.type)
45+
) {
46+
result.push(doc.createTextNode(token.toString()));
47+
continue;
48+
}
49+
50+
let href = token.toHref(opts.defaultProtocol);
51+
let formatted = options.resolve(opts.format, token.toString(), token.type);
52+
let formattedHref = options.resolve(opts.formatHref, href, token.type);
53+
let attributesHash = options.resolve(opts.attributes, href, token.type);
54+
let tagName = options.resolve(opts.tagName, href, token.type);
55+
let linkClass = options.resolve(opts.linkClass, href, token.type);
56+
let target = options.resolve(opts.target, href, token.type);
57+
let events = options.resolve(opts.events, href, token.type);
58+
59+
// Build the link
60+
let link = doc.createElement(tagName);
61+
link.setAttribute('href', formattedHref);
62+
link.setAttribute('class', linkClass);
63+
if (target) {
64+
link.setAttribute('target', target);
65+
}
66+
67+
// Build up additional attributes
68+
if (attributesHash) {
69+
for (var attr in attributesHash) {
70+
link.setAttribute(attr, attributesHash[attr]);
6471
}
72+
}
6573

66-
if (events) {
67-
for (var event in events) {
68-
if (link.addEventListener) {
69-
link.addEventListener(event, events[event]);
70-
} else if (link.attachEvent) {
71-
link.attachEvent('on' + event, events[event]);
72-
}
74+
if (events) {
75+
for (var event in events) {
76+
if (link.addEventListener) {
77+
link.addEventListener(event, events[event]);
78+
} else if (link.attachEvent) {
79+
link.attachEvent('on' + event, events[event]);
7380
}
7481
}
75-
76-
link.appendChild(doc.createTextNode(formatted));
77-
result.push(link);
78-
79-
} else if (token.type === 'nl' && opts.nl2br) {
80-
result.push(doc.createElement('br'));
81-
} else {
82-
result.push(doc.createTextNode(token.toString()));
8382
}
83+
84+
link.appendChild(doc.createTextNode(formatted));
85+
result.push(link);
8486
}
8587

8688
return result;
@@ -112,15 +114,20 @@ function linkifyElementHelper(element, opts, doc) {
112114
break;
113115
case TXT_NODE:
114116

115-
let
116-
str = childElement.nodeValue,
117-
tokens = tokenize(str),
118-
nodes = tokensToNodes(tokens, opts, doc);
117+
let str = childElement.nodeValue;
118+
let tokens = tokenize(str);
119+
120+
if (tokens.length === 0 || tokens.length === 1 && tokens[0] instanceof TEXT_TOKEN) {
121+
// No node replacement required
122+
break;
123+
}
124+
125+
let nodes = tokensToNodes(tokens, opts, doc);
119126

120127
// Swap out the current child for the set of nodes
121128
replaceChildWithChildren(element, childElement, nodes);
122129

123-
// so that the correct sibling is selected
130+
// so that the correct sibling is selected next
124131
childElement = nodes[nodes.length - 1];
125132

126133
break;

src/linkify-string.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function attributesToString(attributes) {
2424
let result = [];
2525

2626
for (let attr in attributes) {
27-
let val = (attributes[attr] + '').replace(/"/g, '&quot;');
27+
let val = attributes[attr] + '';
2828
result.push(`${attr}="${escapeAttr(val)}"`);
2929
}
3030
return result.join(' ');

test/spec/linkify-element-test.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,12 @@ describe('linkify-element', function () {
5454
});
5555

5656
it('Works with default options', function () {
57-
expect(testContainer).to.be.ok;
58-
expect(testContainer).to.be.a('object');
5957
var result = linkifyElement(testContainer, null, doc);
6058
expect(result).to.equal(testContainer); // should return the same element
6159
expect(htmlOptions.linkified).to.contain(testContainer.innerHTML);
6260
});
6361

6462
it('Works with overriden options (general)', function () {
65-
expect(testContainer).to.be.ok;
66-
expect(testContainer).to.be.a('object');
6763
var result = linkifyElement(testContainer, htmlOptions.altOptions, doc);
6864
expect(result).to.equal(testContainer); // should return the same element
6965
expect(htmlOptions.linkifiedAlt).to.contain(testContainer.innerHTML);
@@ -81,10 +77,15 @@ describe('linkify-element', function () {
8177
});
8278

8379
it('Works with overriden options (validate)', function () {
84-
expect(testContainer).to.be.ok;
85-
expect(testContainer).to.be.a('object');
8680
var result = linkifyElement(testContainer, htmlOptions.validateOptions, doc);
8781
expect(result).to.equal(testContainer); // should return the same element
8882
expect(htmlOptions.linkifiedValidate).to.contain(testContainer.innerHTML);
8983
});
84+
85+
it('Works when there is an empty text nodes', function () {
86+
testContainer.appendChild(doc.createTextNode(''));
87+
var result = linkifyElement(testContainer, null, doc);
88+
expect(result).to.equal(testContainer); // should return the same element
89+
expect(htmlOptions.linkified).to.contain(testContainer.innerHTML);
90+
});
9091
});

test/spec/linkify-jquery-test.js

Lines changed: 1 addition & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,50 +4,6 @@ $, doc, testContainer, jsdom,
44
applyLinkify = require('../../lib/linkify-jquery').default,
55
htmlOptions = require('./html/options');
66

7-
if (typeof Object.create != 'function') {
8-
// Production steps of ECMA-262, Edition 5, 15.2.3.5
9-
// Reference: http://es5.github.io/#x15.2.3.5
10-
Object.create = (function() {
11-
// To save on memory, use a shared constructor
12-
function Temp() {}
13-
14-
// make a safe reference to Object.prototype.hasOwnProperty
15-
var hasOwn = Object.prototype.hasOwnProperty;
16-
17-
return function (O) {
18-
// 1. If Type(O) is not Object or Null throw a TypeError exception.
19-
if (typeof O != 'object') {
20-
throw TypeError('Object prototype may only be an Object or null');
21-
}
22-
23-
// 2. Let obj be the result of creating a new object as if by the
24-
// expression new Object() where Object is the standard built-in
25-
// constructor with that name
26-
// 3. Set the [[Prototype]] internal property of obj to O.
27-
Temp.prototype = O;
28-
var obj = new Temp();
29-
Temp.prototype = null; // Let's not keep a stray reference to O...
30-
31-
// 4. If the argument Properties is present and not undefined, add
32-
// own properties to obj as if by calling the standard built-in
33-
// function Object.defineProperties with arguments obj and
34-
// Properties.
35-
if (arguments.length > 1) {
36-
// Object.defineProperties does ToObject on its first argument.
37-
var Properties = Object(arguments[1]);
38-
for (var prop in Properties) {
39-
if (hasOwn.call(Properties, prop)) {
40-
obj[prop] = Properties[prop];
41-
}
42-
}
43-
}
44-
45-
// 5. Return obj
46-
return obj;
47-
};
48-
})();
49-
}
50-
517
try {
528
doc = document;
539
$ = require('jquery'); // should be available through Browserify
@@ -104,7 +60,7 @@ describe('linkify-jquery', function () {
10460
});
10561

10662
// This works but is inconsistent across browsers
107-
xit('Works with the DOM Data API', function () {
63+
it('Works with the DOM Data API', function () {
10864
expect($('header').first().html()).to.be.eql(
10965
'Have a link to:<br><a href="https://github.com" class="linkified" target="_blank">github.com</a>!'
11066
);

0 commit comments

Comments
 (0)