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

Feat linky 12558 #13061

Merged
merged 2 commits into from
Oct 9, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 61 additions & 23 deletions src/ngSanitize/filter/linky.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,39 @@
* @kind function
*
* @description
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
* Finds links in text input and turns them into html links. Supports `http/https/ftp/mailto` and
* plain email address links.
*
* Requires the {@link ngSanitize `ngSanitize`} module to be installed.
*
* @param {string} text Input text.
* @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
* @returns {string} Html-linkified text.
* @param {string} target Window (`_blank|_self|_parent|_top`) or named frame to open links in.
* @param {object|function(url)} [attributes] Add custom attributes to the link element.
*
* Can be one of:
*
* - `object`: A map of attributes
* - `function`: Takes the url as a parameter and returns a map of attributes
*
* If the map of attributes contains a value for `target`, it overrides the value of
* the target parameter.
*
*
* @returns {string} Html-linkified and {@link $sanitize sanitized} text.
*
* @usage
<span ng-bind-html="linky_expression | linky"></span>
*
* @example
<example module="linkyExample" deps="angular-sanitize.js">
<file name="index.html">
<script>
angular.module('linkyExample', ['ngSanitize'])
.controller('ExampleController', ['$scope', function($scope) {
$scope.snippet =
'Pretty text with some links:\n'+
'http://angularjs.org/,\n'+
'mailto:[email protected],\n'+
'[email protected],\n'+
'and one more: ftp://127.0.0.1/.';
$scope.snippetWithTarget = 'http://angularjs.org/';
}]);
</script>
<div ng-controller="ExampleController">
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
<table>
<tr>
<td>Filter</td>
<td>Source</td>
<td>Rendered</td>
<th>Filter</th>
<th>Source</th>
<th>Rendered</th>
</tr>
<tr id="linky-filter">
<td>linky filter</td>
Expand All @@ -55,10 +54,19 @@
<tr id="linky-target">
<td>linky target</td>
<td>
<pre>&lt;div ng-bind-html="snippetWithTarget | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
<pre>&lt;div ng-bind-html="snippetWithSingleURL | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
</td>
<td>
<div ng-bind-html="snippetWithSingleURL | linky:'_blank'"></div>
</td>
</tr>
<tr id="linky-custom-attributes">
<td>linky custom attributes</td>
<td>
<pre>&lt;div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"&gt;<br>&lt;/div&gt;</pre>
</td>
<td>
<div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
<div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"></div>
</td>
</tr>
<tr id="escaped-html">
Expand All @@ -68,6 +76,18 @@
</tr>
</table>
</file>
<file name="script.js">
angular.module('linkyExample', ['ngSanitize'])
.controller('ExampleController', ['$scope', function($scope) {
$scope.snippet =
'Pretty text with some links:\n'+
'http://angularjs.org/,\n'+
'mailto:[email protected],\n'+
'[email protected],\n'+
'and one more: ftp://127.0.0.1/.';
$scope.snippetWithSingleURL = 'http://angularjs.org/';
}]);
</file>
<file name="protractor.js" type="protractor">
it('should linkify the snippet with urls', function() {
expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
Expand Down Expand Up @@ -95,10 +115,17 @@

it('should work with the target property', function() {
expect(element(by.id('linky-target')).
element(by.binding("snippetWithTarget | linky:'_blank'")).getText()).
element(by.binding("snippetWithSingleURL | linky:'_blank'")).getText()).
toBe('http://angularjs.org/');
expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
});

it('should optionally add custom attributes', function() {
expect(element(by.id('linky-custom-attributes')).
element(by.binding("snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}")).getText()).
toBe('http://angularjs.org/');
expect(element(by.css('#linky-custom-attributes a')).getAttribute('rel')).toEqual('nofollow');
});
</file>
</example>
*/
Expand All @@ -107,7 +134,7 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i,
MAILTO_REGEXP = /^mailto:/i;

return function(text, target) {
return function(text, target, attributes) {
if (!text) return text;
var match;
var raw = text;
Expand Down Expand Up @@ -137,8 +164,19 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
}

function addLink(url, text) {
var key;
html.push('<a ');
if (angular.isDefined(target)) {
if (angular.isFunction(attributes)) {
attributes = attributes(url);
}
if (angular.isObject(attributes)) {
for (key in attributes) {
html.push(key + '="' + attributes[key] + '" ');
}
} else {
attributes = {};
}
if (angular.isDefined(target) && !('target' in attributes)) {
html.push('target="',
target,
'" ');
Expand Down
39 changes: 39 additions & 0 deletions test/ngSanitize/filter/linkySpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,43 @@ describe('linky', function() {
toBeOneOf('<a target="someNamedIFrame" href="http://example.com">http://example.com</a>',
'<a href="http://example.com" target="someNamedIFrame">http://example.com</a>');
});

describe('custom attributes', function() {

it('should optionally add custom attributes', function() {
expect(linky("http://example.com", "_self", {rel: "nofollow"})).
toBeOneOf('<a rel="nofollow" target="_self" href="http://example.com">http://example.com</a>',
'<a href="http://example.com" target="_self" rel="nofollow">http://example.com</a>');
});


it('should override target parameter with custom attributes', function() {
expect(linky("http://example.com", "_self", {target: "_blank"})).
toBeOneOf('<a target="_blank" href="http://example.com">http://example.com</a>',
'<a href="http://example.com" target="_blank">http://example.com</a>');
});


it('should optionally add custom attributes from function', function() {
expect(linky("http://example.com", "_self", function(url) {return {"class": "blue"};})).
toBeOneOf('<a class="blue" target="_self" href="http://example.com">http://example.com</a>',
'<a href="http://example.com" target="_self" class="blue">http://example.com</a>',
'<a class="blue" href="http://example.com" target="_self">http://example.com</a>');
});


it('should pass url as parameter to custom attribute function', function() {
var linkParameters = jasmine.createSpy('linkParameters').andReturn({"class": "blue"});
linky("http://example.com", "_self", linkParameters);
expect(linkParameters).toHaveBeenCalledWith('http://example.com');
});


it('should strip unsafe attributes', function() {
expect(linky("http://example.com", "_self", {"class": "blue", "onclick": "alert('Hi')"})).
toBeOneOf('<a class="blue" target="_self" href="http://example.com">http://example.com</a>',
'<a href="http://example.com" target="_self" class="blue">http://example.com</a>',
'<a class="blue" href="http://example.com" target="_self">http://example.com</a>');
});
});
});