angularjs-rails 1.2.14 → 1.2.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/angularjs-rails/version.rb +2 -2
- data/vendor/assets/javascripts/angular-animate.js +4 -5
- data/vendor/assets/javascripts/angular-cookies.js +8 -9
- data/vendor/assets/javascripts/angular-loader.js +6 -8
- data/vendor/assets/javascripts/angular-mocks.js +10 -11
- data/vendor/assets/javascripts/angular-resource.js +13 -3
- data/vendor/assets/javascripts/angular-route.js +139 -134
- data/vendor/assets/javascripts/angular-sanitize.js +2 -2
- data/vendor/assets/javascripts/angular-scenario.js +190 -81
- data/vendor/assets/javascripts/angular-touch.js +16 -2
- data/vendor/assets/javascripts/angular.js +196 -81
- data/vendor/assets/javascripts/unstable/angular-animate.js +1613 -0
- data/vendor/assets/javascripts/unstable/angular-cookies.js +40 -29
- data/vendor/assets/javascripts/unstable/angular-loader.js +166 -58
- data/vendor/assets/javascripts/unstable/angular-mocks.js +832 -535
- data/vendor/assets/javascripts/unstable/angular-resource.js +266 -196
- data/vendor/assets/javascripts/unstable/angular-route.js +927 -0
- data/vendor/assets/javascripts/unstable/angular-sanitize.js +246 -180
- data/vendor/assets/javascripts/unstable/angular-scenario.js +19167 -13895
- data/vendor/assets/javascripts/unstable/{angular-mobile.js → angular-touch.js} +241 -126
- data/vendor/assets/javascripts/unstable/angular.js +12891 -8032
- metadata +5 -3
@@ -1,15 +1,25 @@
|
|
1
1
|
/**
|
2
|
-
* @license AngularJS v1.
|
3
|
-
* (c) 2010-
|
2
|
+
* @license AngularJS v1.3.0-beta.3
|
3
|
+
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
4
4
|
* License: MIT
|
5
5
|
*/
|
6
|
-
(function(window, angular, undefined) {
|
7
|
-
|
6
|
+
(function(window, angular, undefined) {'use strict';
|
7
|
+
|
8
|
+
var $sanitizeMinErr = angular.$$minErr('$sanitize');
|
8
9
|
|
9
10
|
/**
|
10
|
-
* @ngdoc
|
11
|
+
* @ngdoc module
|
11
12
|
* @name ngSanitize
|
12
13
|
* @description
|
14
|
+
*
|
15
|
+
* # ngSanitize
|
16
|
+
*
|
17
|
+
* The `ngSanitize` module provides functionality to sanitize HTML.
|
18
|
+
*
|
19
|
+
*
|
20
|
+
* <div doc-module-components="ngSanitize"></div>
|
21
|
+
*
|
22
|
+
* See {@link ngSanitize.$sanitize `$sanitize`} for usage.
|
13
23
|
*/
|
14
24
|
|
15
25
|
/*
|
@@ -31,7 +41,7 @@
|
|
31
41
|
|
32
42
|
/**
|
33
43
|
* @ngdoc service
|
34
|
-
* @name
|
44
|
+
* @name $sanitize
|
35
45
|
* @function
|
36
46
|
*
|
37
47
|
* @description
|
@@ -40,97 +50,124 @@
|
|
40
50
|
* it into the returned string, however, since our parser is more strict than a typical browser
|
41
51
|
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a
|
42
52
|
* browser, won't make it through the sanitizer.
|
53
|
+
* The whitelist is configured using the functions `aHrefSanitizationWhitelist` and
|
54
|
+
* `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.
|
43
55
|
*
|
44
56
|
* @param {string} html Html input.
|
45
57
|
* @returns {string} Sanitized html.
|
46
58
|
*
|
47
59
|
* @example
|
48
|
-
<
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
60
|
+
<example module="ngSanitize" deps="angular-sanitize.js">
|
61
|
+
<file name="index.html">
|
62
|
+
<script>
|
63
|
+
function Ctrl($scope, $sce) {
|
64
|
+
$scope.snippet =
|
65
|
+
'<p style="color:blue">an html\n' +
|
66
|
+
'<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
|
67
|
+
'snippet</p>';
|
68
|
+
$scope.deliberatelyTrustDangerousSnippet = function() {
|
69
|
+
return $sce.trustAsHtml($scope.snippet);
|
70
|
+
};
|
71
|
+
}
|
72
|
+
</script>
|
73
|
+
<div ng-controller="Ctrl">
|
74
|
+
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
|
75
|
+
<table>
|
76
|
+
<tr>
|
77
|
+
<td>Directive</td>
|
78
|
+
<td>How</td>
|
79
|
+
<td>Source</td>
|
80
|
+
<td>Rendered</td>
|
81
|
+
</tr>
|
82
|
+
<tr id="bind-html-with-sanitize">
|
83
|
+
<td>ng-bind-html</td>
|
84
|
+
<td>Automatically uses $sanitize</td>
|
85
|
+
<td><pre><div ng-bind-html="snippet"><br/></div></pre></td>
|
86
|
+
<td><div ng-bind-html="snippet"></div></td>
|
87
|
+
</tr>
|
88
|
+
<tr id="bind-html-with-trust">
|
89
|
+
<td>ng-bind-html</td>
|
90
|
+
<td>Bypass $sanitize by explicitly trusting the dangerous value</td>
|
91
|
+
<td>
|
92
|
+
<pre><div ng-bind-html="deliberatelyTrustDangerousSnippet()">
|
93
|
+
</div></pre>
|
94
|
+
</td>
|
95
|
+
<td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td>
|
96
|
+
</tr>
|
97
|
+
<tr id="bind-default">
|
98
|
+
<td>ng-bind</td>
|
99
|
+
<td>Automatically escapes</td>
|
100
|
+
<td><pre><div ng-bind="snippet"><br/></div></pre></td>
|
101
|
+
<td><div ng-bind="snippet"></div></td>
|
102
|
+
</tr>
|
103
|
+
</table>
|
104
|
+
</div>
|
105
|
+
</file>
|
106
|
+
<file name="protractor.js" type="protractor">
|
107
|
+
it('should sanitize the html snippet by default', function() {
|
108
|
+
expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
|
109
|
+
toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
|
110
|
+
});
|
111
|
+
|
112
|
+
it('should inline raw snippet if bound to a trusted value', function() {
|
113
|
+
expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).
|
114
|
+
toBe("<p style=\"color:blue\">an html\n" +
|
115
|
+
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
116
|
+
"snippet</p>");
|
117
|
+
});
|
118
|
+
|
119
|
+
it('should escape snippet without any filter', function() {
|
120
|
+
expect(element(by.css('#bind-default div')).getInnerHtml()).
|
121
|
+
toBe("<p style=\"color:blue\">an html\n" +
|
122
|
+
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
|
123
|
+
"snippet</p>");
|
124
|
+
});
|
125
|
+
|
126
|
+
it('should update', function() {
|
127
|
+
element(by.model('snippet')).clear();
|
128
|
+
element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>');
|
129
|
+
expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
|
130
|
+
toBe('new <b>text</b>');
|
131
|
+
expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe(
|
132
|
+
'new <b onclick="alert(1)">text</b>');
|
133
|
+
expect(element(by.css('#bind-default div')).getInnerHtml()).toBe(
|
134
|
+
"new <b onclick=\"alert(1)\">text</b>");
|
135
|
+
});
|
136
|
+
</file>
|
137
|
+
</example>
|
116
138
|
*/
|
117
|
-
|
139
|
+
function $SanitizeProvider() {
|
140
|
+
this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
|
141
|
+
return function(html) {
|
142
|
+
var buf = [];
|
143
|
+
htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
|
144
|
+
return !/^unsafe/.test($$sanitizeUri(uri, isImage));
|
145
|
+
}));
|
146
|
+
return buf.join('');
|
147
|
+
};
|
148
|
+
}];
|
149
|
+
}
|
150
|
+
|
151
|
+
function sanitizeText(chars) {
|
118
152
|
var buf = [];
|
119
|
-
|
120
|
-
|
121
|
-
|
153
|
+
var writer = htmlSanitizeWriter(buf, angular.noop);
|
154
|
+
writer.chars(chars);
|
155
|
+
return buf.join('');
|
156
|
+
}
|
122
157
|
|
123
158
|
|
124
159
|
// Regular Expressions for parsing tags and attributes
|
125
|
-
var START_TAG_REGEXP =
|
160
|
+
var START_TAG_REGEXP =
|
161
|
+
/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
|
126
162
|
END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/,
|
127
163
|
ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
|
128
164
|
BEGIN_TAG_REGEXP = /^</,
|
129
165
|
BEGING_END_TAGE_REGEXP = /^<\s*\//,
|
130
166
|
COMMENT_REGEXP = /<!--(.*?)-->/g,
|
167
|
+
DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i,
|
131
168
|
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
|
132
|
-
|
133
|
-
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
|
169
|
+
// Match everything outside of normal chars and " (quote character)
|
170
|
+
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
|
134
171
|
|
135
172
|
|
136
173
|
// Good source of info about elements and attributes
|
@@ -145,23 +182,29 @@ var voidElements = makeMap("area,br,col,hr,img,wbr");
|
|
145
182
|
// http://dev.w3.org/html5/spec/Overview.html#optional-tags
|
146
183
|
var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
|
147
184
|
optionalEndTagInlineElements = makeMap("rp,rt"),
|
148
|
-
optionalEndTagElements = angular.extend({},
|
185
|
+
optionalEndTagElements = angular.extend({},
|
186
|
+
optionalEndTagInlineElements,
|
187
|
+
optionalEndTagBlockElements);
|
149
188
|
|
150
189
|
// Safe Block Elements - HTML5
|
151
|
-
var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article,
|
152
|
-
"blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,
|
153
|
-
"header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
|
190
|
+
var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," +
|
191
|
+
"aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," +
|
192
|
+
"h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
|
154
193
|
|
155
194
|
// Inline Elements - HTML5
|
156
|
-
var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b,
|
157
|
-
"big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,
|
158
|
-
"span,strike,strong,sub,sup,time,tt,u,var"));
|
195
|
+
var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," +
|
196
|
+
"bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +
|
197
|
+
"samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
|
159
198
|
|
160
199
|
|
161
200
|
// Special Elements (can contain anything)
|
162
201
|
var specialElements = makeMap("script,style");
|
163
202
|
|
164
|
-
var validElements = angular.extend({},
|
203
|
+
var validElements = angular.extend({},
|
204
|
+
voidElements,
|
205
|
+
blockElements,
|
206
|
+
inlineElements,
|
207
|
+
optionalEndTagElements);
|
165
208
|
|
166
209
|
//Attributes that have href and hence need to be sanitized
|
167
210
|
var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap");
|
@@ -169,7 +212,7 @@ var validAttrs = angular.extend({}, uriAttrs, makeMap(
|
|
169
212
|
'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
|
170
213
|
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
|
171
214
|
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
|
172
|
-
'scope,scrolling,shape,span,start,summary,target,title,type,'+
|
215
|
+
'scope,scrolling,shape,size,span,start,summary,target,title,type,'+
|
173
216
|
'valign,value,vspace,width'));
|
174
217
|
|
175
218
|
function makeMap(str) {
|
@@ -203,14 +246,22 @@ function htmlParser( html, handler ) {
|
|
203
246
|
|
204
247
|
// Comment
|
205
248
|
if ( html.indexOf("<!--") === 0 ) {
|
206
|
-
|
249
|
+
// comments containing -- are not allowed unless they terminate the comment
|
250
|
+
index = html.indexOf("--", 4);
|
207
251
|
|
208
|
-
if ( index >= 0 ) {
|
252
|
+
if ( index >= 0 && html.lastIndexOf("-->", index) === index) {
|
209
253
|
if (handler.comment) handler.comment( html.substring( 4, index ) );
|
210
254
|
html = html.substring( index + 3 );
|
211
255
|
chars = false;
|
212
256
|
}
|
257
|
+
// DOCTYPE
|
258
|
+
} else if ( DOCTYPE_REGEXP.test(html) ) {
|
259
|
+
match = html.match( DOCTYPE_REGEXP );
|
213
260
|
|
261
|
+
if ( match ) {
|
262
|
+
html = html.replace( match[0], '');
|
263
|
+
chars = false;
|
264
|
+
}
|
214
265
|
// end tag
|
215
266
|
} else if ( BEGING_END_TAGE_REGEXP.test(html) ) {
|
216
267
|
match = html.match( END_TAG_REGEXP );
|
@@ -242,21 +293,21 @@ function htmlParser( html, handler ) {
|
|
242
293
|
}
|
243
294
|
|
244
295
|
} else {
|
245
|
-
html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
|
246
|
-
|
247
|
-
replace(COMMENT_REGEXP, "$1").
|
248
|
-
replace(CDATA_REGEXP, "$1");
|
296
|
+
html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
|
297
|
+
function(all, text){
|
298
|
+
text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1");
|
249
299
|
|
250
|
-
|
300
|
+
if (handler.chars) handler.chars( decodeEntities(text) );
|
251
301
|
|
252
|
-
|
302
|
+
return "";
|
253
303
|
});
|
254
304
|
|
255
305
|
parseEndTag( "", stack.last() );
|
256
306
|
}
|
257
307
|
|
258
308
|
if ( html == last ) {
|
259
|
-
throw "
|
309
|
+
throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " +
|
310
|
+
"of html: {0}", html);
|
260
311
|
}
|
261
312
|
last = html;
|
262
313
|
}
|
@@ -283,13 +334,14 @@ function htmlParser( html, handler ) {
|
|
283
334
|
|
284
335
|
var attrs = {};
|
285
336
|
|
286
|
-
rest.replace(ATTR_REGEXP,
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
337
|
+
rest.replace(ATTR_REGEXP,
|
338
|
+
function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
|
339
|
+
var value = doubleQuotedValue
|
340
|
+
|| singleQuotedValue
|
341
|
+
|| unquotedValue
|
342
|
+
|| '';
|
291
343
|
|
292
|
-
|
344
|
+
attrs[name] = decodeEntities(value);
|
293
345
|
});
|
294
346
|
if (handler.start) handler.start( tagName, attrs, unary );
|
295
347
|
}
|
@@ -314,15 +366,32 @@ function htmlParser( html, handler ) {
|
|
314
366
|
}
|
315
367
|
}
|
316
368
|
|
369
|
+
var hiddenPre=document.createElement("pre");
|
370
|
+
var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
|
317
371
|
/**
|
318
372
|
* decodes all entities into regular string
|
319
373
|
* @param value
|
320
374
|
* @returns {string} A string with decoded entities.
|
321
375
|
*/
|
322
|
-
var hiddenPre=document.createElement("pre");
|
323
376
|
function decodeEntities(value) {
|
324
|
-
|
325
|
-
|
377
|
+
if (!value) { return ''; }
|
378
|
+
|
379
|
+
// Note: IE8 does not preserve spaces at the start/end of innerHTML
|
380
|
+
// so we must capture them and reattach them afterward
|
381
|
+
var parts = spaceRe.exec(value);
|
382
|
+
var spaceBefore = parts[1];
|
383
|
+
var spaceAfter = parts[3];
|
384
|
+
var content = parts[2];
|
385
|
+
if (content) {
|
386
|
+
hiddenPre.innerHTML=content.replace(/</g,"<");
|
387
|
+
// innerText depends on styling as it doesn't display hidden elements.
|
388
|
+
// Therefore, it's better to use textContent not to cause unnecessary
|
389
|
+
// reflows. However, IE<9 don't support textContent so the innerText
|
390
|
+
// fallback is necessary.
|
391
|
+
content = 'textContent' in hiddenPre ?
|
392
|
+
hiddenPre.textContent : hiddenPre.innerText;
|
393
|
+
}
|
394
|
+
return spaceBefore + content + spaceAfter;
|
326
395
|
}
|
327
396
|
|
328
397
|
/**
|
@@ -330,7 +399,7 @@ function decodeEntities(value) {
|
|
330
399
|
* resulting string can be safely inserted into attribute or
|
331
400
|
* element text.
|
332
401
|
* @param value
|
333
|
-
* @returns escaped text
|
402
|
+
* @returns {string} escaped text
|
334
403
|
*/
|
335
404
|
function encodeEntities(value) {
|
336
405
|
return value.
|
@@ -352,7 +421,7 @@ function encodeEntities(value) {
|
|
352
421
|
* comment: function(text) {}
|
353
422
|
* }
|
354
423
|
*/
|
355
|
-
function htmlSanitizeWriter(buf){
|
424
|
+
function htmlSanitizeWriter(buf, uriValidator){
|
356
425
|
var ignore = false;
|
357
426
|
var out = angular.bind(buf, buf.push);
|
358
427
|
return {
|
@@ -361,12 +430,14 @@ function htmlSanitizeWriter(buf){
|
|
361
430
|
if (!ignore && specialElements[tag]) {
|
362
431
|
ignore = tag;
|
363
432
|
}
|
364
|
-
if (!ignore && validElements[tag]
|
433
|
+
if (!ignore && validElements[tag] === true) {
|
365
434
|
out('<');
|
366
435
|
out(tag);
|
367
436
|
angular.forEach(attrs, function(value, key){
|
368
437
|
var lkey=angular.lowercase(key);
|
369
|
-
|
438
|
+
var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
|
439
|
+
if (validAttrs[lkey] === true &&
|
440
|
+
(uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
|
370
441
|
out(' ');
|
371
442
|
out(key);
|
372
443
|
out('="');
|
@@ -379,7 +450,7 @@ function htmlSanitizeWriter(buf){
|
|
379
450
|
},
|
380
451
|
end: function(tag){
|
381
452
|
tag = angular.lowercase(tag);
|
382
|
-
if (!ignore && validElements[tag]
|
453
|
+
if (!ignore && validElements[tag] === true) {
|
383
454
|
out('</');
|
384
455
|
out(tag);
|
385
456
|
out('>');
|
@@ -398,39 +469,20 @@ function htmlSanitizeWriter(buf){
|
|
398
469
|
|
399
470
|
|
400
471
|
// define ngSanitize module and register $sanitize service
|
401
|
-
angular.module('ngSanitize', []).
|
472
|
+
angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
|
402
473
|
|
403
|
-
|
404
|
-
* @ngdoc directive
|
405
|
-
* @name ngSanitize.directive:ngBindHtml
|
406
|
-
*
|
407
|
-
* @description
|
408
|
-
* Creates a binding that will sanitize the result of evaluating the `expression` with the
|
409
|
-
* {@link ngSanitize.$sanitize $sanitize} service and innerHTML the result into the current element.
|
410
|
-
*
|
411
|
-
* See {@link ngSanitize.$sanitize $sanitize} docs for examples.
|
412
|
-
*
|
413
|
-
* @element ANY
|
414
|
-
* @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate.
|
415
|
-
*/
|
416
|
-
angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($sanitize) {
|
417
|
-
return function(scope, element, attr) {
|
418
|
-
element.addClass('ng-binding').data('$binding', attr.ngBindHtml);
|
419
|
-
scope.$watch(attr.ngBindHtml, function ngBindHtmlWatchAction(value) {
|
420
|
-
value = $sanitize(value);
|
421
|
-
element.html(value || '');
|
422
|
-
});
|
423
|
-
};
|
424
|
-
}]);
|
474
|
+
/* global sanitizeText: false */
|
425
475
|
|
426
476
|
/**
|
427
477
|
* @ngdoc filter
|
428
|
-
* @name
|
478
|
+
* @name linky
|
429
479
|
* @function
|
430
480
|
*
|
431
481
|
* @description
|
432
|
-
*
|
433
|
-
*
|
482
|
+
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
|
483
|
+
* plain email address links.
|
484
|
+
*
|
485
|
+
* Requires the {@link ngSanitize `ngSanitize`} module to be installed.
|
434
486
|
*
|
435
487
|
* @param {string} text Input text.
|
436
488
|
* @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
|
@@ -440,8 +492,8 @@ angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($san
|
|
440
492
|
<span ng-bind-html="linky_expression | linky"></span>
|
441
493
|
*
|
442
494
|
* @example
|
443
|
-
<
|
444
|
-
<
|
495
|
+
<example module="ngSanitize" deps="angular-sanitize.js">
|
496
|
+
<file name="index.html">
|
445
497
|
<script>
|
446
498
|
function Ctrl($scope) {
|
447
499
|
$scope.snippet =
|
@@ -485,42 +537,44 @@ angular.module('ngSanitize').directive('ngBindHtml', ['$sanitize', function($san
|
|
485
537
|
<td><div ng-bind="snippet"></div></td>
|
486
538
|
</tr>
|
487
539
|
</table>
|
488
|
-
</
|
489
|
-
<
|
540
|
+
</file>
|
541
|
+
<file name="protractor.js" type="protractor">
|
490
542
|
it('should linkify the snippet with urls', function() {
|
491
|
-
expect(
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
'<a href="mailto:another@somewhere.org">another@somewhere.org</a>, ' +
|
496
|
-
'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.');
|
543
|
+
expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
|
544
|
+
toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' +
|
545
|
+
'another@somewhere.org, and one more: ftp://127.0.0.1/.');
|
546
|
+
expect(element.all(by.css('#linky-filter a')).count()).toEqual(4);
|
497
547
|
});
|
498
548
|
|
499
|
-
it
|
500
|
-
expect(
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
"another@somewhere.org,\n" +
|
505
|
-
"and one more: ftp://127.0.0.1/.");
|
549
|
+
it('should not linkify snippet without the linky filter', function() {
|
550
|
+
expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()).
|
551
|
+
toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' +
|
552
|
+
'another@somewhere.org, and one more: ftp://127.0.0.1/.');
|
553
|
+
expect(element.all(by.css('#escaped-html a')).count()).toEqual(0);
|
506
554
|
});
|
507
555
|
|
508
556
|
it('should update', function() {
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
557
|
+
element(by.model('snippet')).clear();
|
558
|
+
element(by.model('snippet')).sendKeys('new http://link.');
|
559
|
+
expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
|
560
|
+
toBe('new http://link.');
|
561
|
+
expect(element.all(by.css('#linky-filter a')).count()).toEqual(1);
|
562
|
+
expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText())
|
563
|
+
.toBe('new http://link.');
|
513
564
|
});
|
514
565
|
|
515
566
|
it('should work with the target property', function() {
|
516
|
-
expect(
|
517
|
-
|
567
|
+
expect(element(by.id('linky-target')).
|
568
|
+
element(by.binding("snippetWithTarget | linky:'_blank'")).getText()).
|
569
|
+
toBe('http://angularjs.org/');
|
570
|
+
expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
|
518
571
|
});
|
519
|
-
</
|
520
|
-
</
|
572
|
+
</file>
|
573
|
+
</example>
|
521
574
|
*/
|
522
|
-
angular.module('ngSanitize').filter('linky', function() {
|
523
|
-
var LINKY_URL_REGEXP =
|
575
|
+
angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
|
576
|
+
var LINKY_URL_REGEXP =
|
577
|
+
/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/,
|
524
578
|
MAILTO_REGEXP = /^mailto:/;
|
525
579
|
|
526
580
|
return function(text, target) {
|
@@ -528,31 +582,43 @@ angular.module('ngSanitize').filter('linky', function() {
|
|
528
582
|
var match;
|
529
583
|
var raw = text;
|
530
584
|
var html = [];
|
531
|
-
// TODO(vojta): use $sanitize instead
|
532
|
-
var writer = htmlSanitizeWriter(html);
|
533
585
|
var url;
|
534
586
|
var i;
|
535
|
-
var properties = {};
|
536
|
-
if (angular.isDefined(target)) {
|
537
|
-
properties.target = target;
|
538
|
-
}
|
539
587
|
while ((match = raw.match(LINKY_URL_REGEXP))) {
|
540
588
|
// We can not end in these as they are sometimes found at the end of the sentence
|
541
589
|
url = match[0];
|
542
590
|
// if we did not match ftp/http/mailto then assume mailto
|
543
591
|
if (match[2] == match[3]) url = 'mailto:' + url;
|
544
592
|
i = match.index;
|
545
|
-
|
546
|
-
|
547
|
-
writer.start('a', properties);
|
548
|
-
writer.chars(match[0].replace(MAILTO_REGEXP, ''));
|
549
|
-
writer.end('a');
|
593
|
+
addText(raw.substr(0, i));
|
594
|
+
addLink(url, match[0].replace(MAILTO_REGEXP, ''));
|
550
595
|
raw = raw.substring(i + match[0].length);
|
551
596
|
}
|
552
|
-
|
553
|
-
return html.join('');
|
597
|
+
addText(raw);
|
598
|
+
return $sanitize(html.join(''));
|
599
|
+
|
600
|
+
function addText(text) {
|
601
|
+
if (!text) {
|
602
|
+
return;
|
603
|
+
}
|
604
|
+
html.push(sanitizeText(text));
|
605
|
+
}
|
606
|
+
|
607
|
+
function addLink(url, text) {
|
608
|
+
html.push('<a ');
|
609
|
+
if (angular.isDefined(target)) {
|
610
|
+
html.push('target="');
|
611
|
+
html.push(target);
|
612
|
+
html.push('" ');
|
613
|
+
}
|
614
|
+
html.push('href="');
|
615
|
+
html.push(url);
|
616
|
+
html.push('">');
|
617
|
+
addText(text);
|
618
|
+
html.push('</a>');
|
619
|
+
}
|
554
620
|
};
|
555
|
-
});
|
621
|
+
}]);
|
556
622
|
|
557
623
|
|
558
624
|
})(window, window.angular);
|