Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 06f002b

Browse files
stianjensenNarretz
authored andcommitted
feat(linky): add support for custom attributes
Optional extra attributes may be defined either as: - a map of attributes and values - a function that takes the url as a parameter and returns a map Closes #12558 Closes #13061
1 parent 8226ff8 commit 06f002b

File tree

2 files changed

+82
-6
lines changed

2 files changed

+82
-6
lines changed

src/ngSanitize/filter/linky.js

+43-6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@
1515
*
1616
* @param {string} text Input text.
1717
* @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
18+
* @param {object|function(url)} [attributes] Add custom attributes to the link element.
19+
*
20+
* Can be one of:
21+
*
22+
* - `object`: A map of attributes
23+
* - `function`: Takes the url as a parameter and returns a map of attributes
24+
*
25+
* If the map of attributes contains a value for `target`, it overrides the value of
26+
* the target parameter.
27+
*
1828
* @returns {string} Html-linkified text.
1929
*
2030
* @usage
@@ -32,7 +42,7 @@
3242
'mailto:us@somewhere.org,\n'+
3343
'another@somewhere.org,\n'+
3444
'and one more: ftp://127.0.0.1/.';
35-
$scope.snippetWithTarget = 'http://angularjs.org/';
45+
$scope.snippetWithSingleURL = 'http://angularjs.org/';
3646
}]);
3747
</script>
3848
<div ng-controller="ExampleController">
@@ -55,10 +65,19 @@
5565
<tr id="linky-target">
5666
<td>linky target</td>
5767
<td>
58-
<pre>&lt;div ng-bind-html="snippetWithTarget | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
68+
<pre>&lt;div ng-bind-html="snippetWithSingleURL | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
69+
</td>
70+
<td>
71+
<div ng-bind-html="snippetWithSingleURL | linky:'_blank'"></div>
72+
</td>
73+
</tr>
74+
<tr id="linky-custom-attributes">
75+
<td>linky custom attributes</td>
76+
<td>
77+
<pre>&lt;div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"&gt;<br>&lt;/div&gt;</pre>
5978
</td>
6079
<td>
61-
<div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
80+
<div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"></div>
6281
</td>
6382
</tr>
6483
<tr id="escaped-html">
@@ -95,10 +114,17 @@
95114
96115
it('should work with the target property', function() {
97116
expect(element(by.id('linky-target')).
98-
element(by.binding("snippetWithTarget | linky:'_blank'")).getText()).
117+
element(by.binding("snippetWithSingleURL | linky:'_blank'")).getText()).
99118
toBe('http://angularjs.org/');
100119
expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
101120
});
121+
122+
it('should optionally add custom attributes', function() {
123+
expect(element(by.id('linky-custom-attributes')).
124+
element(by.binding("snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}")).getText()).
125+
toBe('http://angularjs.org/');
126+
expect(element(by.css('#linky-custom-attributes a')).getAttribute('rel')).toEqual('nofollow');
127+
});
102128
</file>
103129
</example>
104130
*/
@@ -107,7 +133,7 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
107133
/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i,
108134
MAILTO_REGEXP = /^mailto:/i;
109135

110-
return function(text, target) {
136+
return function(text, target, attributes) {
111137
if (!text) return text;
112138
var match;
113139
var raw = text;
@@ -137,8 +163,19 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
137163
}
138164

139165
function addLink(url, text) {
166+
var key;
140167
html.push('<a ');
141-
if (angular.isDefined(target)) {
168+
if (angular.isFunction(attributes)) {
169+
attributes = attributes(url);
170+
}
171+
if (angular.isObject(attributes)) {
172+
for (key in attributes) {
173+
html.push(key + '="' + attributes[key] + '" ');
174+
}
175+
} else {
176+
attributes = {};
177+
}
178+
if (angular.isDefined(target) && !('target' in attributes)) {
142179
html.push('target="',
143180
target,
144181
'" ');

test/ngSanitize/filter/linkySpec.js

+39
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,43 @@ describe('linky', function() {
5656
toBeOneOf('<a target="someNamedIFrame" href="http://example.com">http://example.com</a>',
5757
'<a href="http://example.com" target="someNamedIFrame">http://example.com</a>');
5858
});
59+
60+
describe('custom attributes', function() {
61+
62+
it('should optionally add custom attributes', function() {
63+
expect(linky("http://example.com", "_self", {rel: "nofollow"})).
64+
toBeOneOf('<a rel="nofollow" target="_self" href="http://example.com">http://example.com</a>',
65+
'<a href="http://example.com" target="_self" rel="nofollow">http://example.com</a>');
66+
});
67+
68+
69+
it('should override target parameter with custom attributes', function() {
70+
expect(linky("http://example.com", "_self", {target: "_blank"})).
71+
toBeOneOf('<a target="_blank" href="http://example.com">http://example.com</a>',
72+
'<a href="http://example.com" target="_blank">http://example.com</a>');
73+
});
74+
75+
76+
it('should optionally add custom attributes from function', function() {
77+
expect(linky("http://example.com", "_self", function(url) {return {"class": "blue"};})).
78+
toBeOneOf('<a class="blue" target="_self" href="http://example.com">http://example.com</a>',
79+
'<a href="http://example.com" target="_self" class="blue">http://example.com</a>',
80+
'<a class="blue" href="http://example.com" target="_self">http://example.com</a>');
81+
});
82+
83+
84+
it('should pass url as parameter to custom attribute function', function() {
85+
var linkParameters = jasmine.createSpy('linkParameters').andReturn({"class": "blue"});
86+
linky("http://example.com", "_self", linkParameters);
87+
expect(linkParameters).toHaveBeenCalledWith('http://example.com');
88+
});
89+
90+
91+
it('should strip unsafe attributes', function() {
92+
expect(linky("http://example.com", "_self", {"class": "blue", "onclick": "alert('Hi')"})).
93+
toBeOneOf('<a class="blue" target="_self" href="http://example.com">http://example.com</a>',
94+
'<a href="http://example.com" target="_self" class="blue">http://example.com</a>',
95+
'<a class="blue" href="http://example.com" target="_self">http://example.com</a>');
96+
});
97+
});
5998
});

0 commit comments

Comments
 (0)