angular-gem 1.3.7 → 1.3.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (27) hide show
  1. checksums.yaml +8 -8
  2. data/lib/angular-gem/version.rb +1 -1
  3. data/vendor/assets/javascripts/1.3.8/angular-animate.js +2138 -0
  4. data/vendor/assets/javascripts/1.3.8/angular-aria.js +339 -0
  5. data/vendor/assets/javascripts/1.3.8/angular-cookies.js +206 -0
  6. data/vendor/assets/javascripts/1.3.8/angular-loader.js +405 -0
  7. data/vendor/assets/javascripts/1.3.8/angular-messages.js +400 -0
  8. data/vendor/assets/javascripts/1.3.8/angular-mocks.js +2382 -0
  9. data/vendor/assets/javascripts/1.3.8/angular-resource.js +667 -0
  10. data/vendor/assets/javascripts/1.3.8/angular-route.js +995 -0
  11. data/vendor/assets/javascripts/1.3.8/angular-sanitize.js +680 -0
  12. data/vendor/assets/javascripts/1.3.8/angular-scenario.js +37424 -0
  13. data/vendor/assets/javascripts/1.3.8/angular-touch.js +622 -0
  14. data/vendor/assets/javascripts/1.3.8/angular.js +26070 -0
  15. data/vendor/assets/javascripts/angular-animate.js +3 -2
  16. data/vendor/assets/javascripts/angular-aria.js +20 -13
  17. data/vendor/assets/javascripts/angular-cookies.js +1 -1
  18. data/vendor/assets/javascripts/angular-loader.js +2 -2
  19. data/vendor/assets/javascripts/angular-messages.js +1 -1
  20. data/vendor/assets/javascripts/angular-mocks.js +15 -15
  21. data/vendor/assets/javascripts/angular-resource.js +1 -1
  22. data/vendor/assets/javascripts/angular-route.js +1 -1
  23. data/vendor/assets/javascripts/angular-sanitize.js +1 -1
  24. data/vendor/assets/javascripts/angular-scenario.js +95 -62
  25. data/vendor/assets/javascripts/angular-touch.js +1 -1
  26. data/vendor/assets/javascripts/angular.js +95 -62
  27. metadata +14 -2
@@ -0,0 +1,680 @@
1
+ /**
2
+ * @license AngularJS v1.3.8
3
+ * (c) 2010-2014 Google, Inc. http://angularjs.org
4
+ * License: MIT
5
+ */
6
+ (function(window, angular, undefined) {'use strict';
7
+
8
+ var $sanitizeMinErr = angular.$$minErr('$sanitize');
9
+
10
+ /**
11
+ * @ngdoc module
12
+ * @name ngSanitize
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.
23
+ */
24
+
25
+ /*
26
+ * HTML Parser By Misko Hevery (misko@hevery.com)
27
+ * based on: HTML Parser By John Resig (ejohn.org)
28
+ * Original code by Erik Arvidsson, Mozilla Public License
29
+ * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
30
+ *
31
+ * // Use like so:
32
+ * htmlParser(htmlString, {
33
+ * start: function(tag, attrs, unary) {},
34
+ * end: function(tag) {},
35
+ * chars: function(text) {},
36
+ * comment: function(text) {}
37
+ * });
38
+ *
39
+ */
40
+
41
+
42
+ /**
43
+ * @ngdoc service
44
+ * @name $sanitize
45
+ * @kind function
46
+ *
47
+ * @description
48
+ * The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are
49
+ * then serialized back to properly escaped html string. This means that no unsafe input can make
50
+ * it into the returned string, however, since our parser is more strict than a typical browser
51
+ * parser, it's possible that some obscure input, which would be recognized as valid HTML by a
52
+ * browser, won't make it through the sanitizer. The input may also contain SVG markup.
53
+ * The whitelist is configured using the functions `aHrefSanitizationWhitelist` and
54
+ * `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.
55
+ *
56
+ * @param {string} html HTML input.
57
+ * @returns {string} Sanitized HTML.
58
+ *
59
+ * @example
60
+ <example module="sanitizeExample" deps="angular-sanitize.js">
61
+ <file name="index.html">
62
+ <script>
63
+ angular.module('sanitizeExample', ['ngSanitize'])
64
+ .controller('ExampleController', ['$scope', '$sce', function($scope, $sce) {
65
+ $scope.snippet =
66
+ '<p style="color:blue">an html\n' +
67
+ '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
68
+ 'snippet</p>';
69
+ $scope.deliberatelyTrustDangerousSnippet = function() {
70
+ return $sce.trustAsHtml($scope.snippet);
71
+ };
72
+ }]);
73
+ </script>
74
+ <div ng-controller="ExampleController">
75
+ Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
76
+ <table>
77
+ <tr>
78
+ <td>Directive</td>
79
+ <td>How</td>
80
+ <td>Source</td>
81
+ <td>Rendered</td>
82
+ </tr>
83
+ <tr id="bind-html-with-sanitize">
84
+ <td>ng-bind-html</td>
85
+ <td>Automatically uses $sanitize</td>
86
+ <td><pre>&lt;div ng-bind-html="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
87
+ <td><div ng-bind-html="snippet"></div></td>
88
+ </tr>
89
+ <tr id="bind-html-with-trust">
90
+ <td>ng-bind-html</td>
91
+ <td>Bypass $sanitize by explicitly trusting the dangerous value</td>
92
+ <td>
93
+ <pre>&lt;div ng-bind-html="deliberatelyTrustDangerousSnippet()"&gt;
94
+ &lt;/div&gt;</pre>
95
+ </td>
96
+ <td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td>
97
+ </tr>
98
+ <tr id="bind-default">
99
+ <td>ng-bind</td>
100
+ <td>Automatically escapes</td>
101
+ <td><pre>&lt;div ng-bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
102
+ <td><div ng-bind="snippet"></div></td>
103
+ </tr>
104
+ </table>
105
+ </div>
106
+ </file>
107
+ <file name="protractor.js" type="protractor">
108
+ it('should sanitize the html snippet by default', function() {
109
+ expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
110
+ toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
111
+ });
112
+
113
+ it('should inline raw snippet if bound to a trusted value', function() {
114
+ expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).
115
+ toBe("<p style=\"color:blue\">an html\n" +
116
+ "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
117
+ "snippet</p>");
118
+ });
119
+
120
+ it('should escape snippet without any filter', function() {
121
+ expect(element(by.css('#bind-default div')).getInnerHtml()).
122
+ toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
123
+ "&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
124
+ "snippet&lt;/p&gt;");
125
+ });
126
+
127
+ it('should update', function() {
128
+ element(by.model('snippet')).clear();
129
+ element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>');
130
+ expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
131
+ toBe('new <b>text</b>');
132
+ expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe(
133
+ 'new <b onclick="alert(1)">text</b>');
134
+ expect(element(by.css('#bind-default div')).getInnerHtml()).toBe(
135
+ "new &lt;b onclick=\"alert(1)\"&gt;text&lt;/b&gt;");
136
+ });
137
+ </file>
138
+ </example>
139
+ */
140
+ function $SanitizeProvider() {
141
+ this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
142
+ return function(html) {
143
+ var buf = [];
144
+ htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
145
+ return !/^unsafe/.test($$sanitizeUri(uri, isImage));
146
+ }));
147
+ return buf.join('');
148
+ };
149
+ }];
150
+ }
151
+
152
+ function sanitizeText(chars) {
153
+ var buf = [];
154
+ var writer = htmlSanitizeWriter(buf, angular.noop);
155
+ writer.chars(chars);
156
+ return buf.join('');
157
+ }
158
+
159
+
160
+ // Regular Expressions for parsing tags and attributes
161
+ var START_TAG_REGEXP =
162
+ /^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,
163
+ END_TAG_REGEXP = /^<\/\s*([\w:-]+)[^>]*>/,
164
+ ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
165
+ BEGIN_TAG_REGEXP = /^</,
166
+ BEGING_END_TAGE_REGEXP = /^<\//,
167
+ COMMENT_REGEXP = /<!--(.*?)-->/g,
168
+ DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i,
169
+ CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
170
+ SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
171
+ // Match everything outside of normal chars and " (quote character)
172
+ NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
173
+
174
+
175
+ // Good source of info about elements and attributes
176
+ // http://dev.w3.org/html5/spec/Overview.html#semantics
177
+ // http://simon.html5.org/html-elements
178
+
179
+ // Safe Void Elements - HTML5
180
+ // http://dev.w3.org/html5/spec/Overview.html#void-elements
181
+ var voidElements = makeMap("area,br,col,hr,img,wbr");
182
+
183
+ // Elements that you can, intentionally, leave open (and which close themselves)
184
+ // http://dev.w3.org/html5/spec/Overview.html#optional-tags
185
+ var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
186
+ optionalEndTagInlineElements = makeMap("rp,rt"),
187
+ optionalEndTagElements = angular.extend({},
188
+ optionalEndTagInlineElements,
189
+ optionalEndTagBlockElements);
190
+
191
+ // Safe Block Elements - HTML5
192
+ var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," +
193
+ "aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," +
194
+ "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
195
+
196
+ // Inline Elements - HTML5
197
+ var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," +
198
+ "bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +
199
+ "samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
200
+
201
+ // SVG Elements
202
+ // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements
203
+ var svgElements = makeMap("animate,animateColor,animateMotion,animateTransform,circle,defs," +
204
+ "desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,hkern,image,linearGradient," +
205
+ "line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,radialGradient,rect,set," +
206
+ "stop,svg,switch,text,title,tspan,use");
207
+
208
+ // Special Elements (can contain anything)
209
+ var specialElements = makeMap("script,style");
210
+
211
+ var validElements = angular.extend({},
212
+ voidElements,
213
+ blockElements,
214
+ inlineElements,
215
+ optionalEndTagElements,
216
+ svgElements);
217
+
218
+ //Attributes that have href and hence need to be sanitized
219
+ var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap,xlink:href");
220
+
221
+ var htmlAttrs = makeMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
222
+ 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
223
+ 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
224
+ 'scope,scrolling,shape,size,span,start,summary,target,title,type,' +
225
+ 'valign,value,vspace,width');
226
+
227
+ // SVG attributes (without "id" and "name" attributes)
228
+ // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes
229
+ var svgAttrs = makeMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
230
+ 'attributeName,attributeType,baseProfile,bbox,begin,by,calcMode,cap-height,class,color,' +
231
+ 'color-rendering,content,cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,' +
232
+ 'font-size,font-stretch,font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,' +
233
+ 'gradientUnits,hanging,height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,' +
234
+ 'keySplines,keyTimes,lang,marker-end,marker-mid,marker-start,markerHeight,markerUnits,' +
235
+ 'markerWidth,mathematical,max,min,offset,opacity,orient,origin,overline-position,' +
236
+ 'overline-thickness,panose-1,path,pathLength,points,preserveAspectRatio,r,refX,refY,' +
237
+ 'repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,' +
238
+ 'stemv,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,stroke,' +
239
+ 'stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,' +
240
+ 'stroke-opacity,stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,' +
241
+ 'underline-position,underline-thickness,unicode,unicode-range,units-per-em,values,version,' +
242
+ 'viewBox,visibility,width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,' +
243
+ 'xlink:show,xlink:title,xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,' +
244
+ 'zoomAndPan');
245
+
246
+ var validAttrs = angular.extend({},
247
+ uriAttrs,
248
+ svgAttrs,
249
+ htmlAttrs);
250
+
251
+ function makeMap(str) {
252
+ var obj = {}, items = str.split(','), i;
253
+ for (i = 0; i < items.length; i++) obj[items[i]] = true;
254
+ return obj;
255
+ }
256
+
257
+
258
+ /**
259
+ * @example
260
+ * htmlParser(htmlString, {
261
+ * start: function(tag, attrs, unary) {},
262
+ * end: function(tag) {},
263
+ * chars: function(text) {},
264
+ * comment: function(text) {}
265
+ * });
266
+ *
267
+ * @param {string} html string
268
+ * @param {object} handler
269
+ */
270
+ function htmlParser(html, handler) {
271
+ if (typeof html !== 'string') {
272
+ if (html === null || typeof html === 'undefined') {
273
+ html = '';
274
+ } else {
275
+ html = '' + html;
276
+ }
277
+ }
278
+ var index, chars, match, stack = [], last = html, text;
279
+ stack.last = function() { return stack[ stack.length - 1 ]; };
280
+
281
+ while (html) {
282
+ text = '';
283
+ chars = true;
284
+
285
+ // Make sure we're not in a script or style element
286
+ if (!stack.last() || !specialElements[ stack.last() ]) {
287
+
288
+ // Comment
289
+ if (html.indexOf("<!--") === 0) {
290
+ // comments containing -- are not allowed unless they terminate the comment
291
+ index = html.indexOf("--", 4);
292
+
293
+ if (index >= 0 && html.lastIndexOf("-->", index) === index) {
294
+ if (handler.comment) handler.comment(html.substring(4, index));
295
+ html = html.substring(index + 3);
296
+ chars = false;
297
+ }
298
+ // DOCTYPE
299
+ } else if (DOCTYPE_REGEXP.test(html)) {
300
+ match = html.match(DOCTYPE_REGEXP);
301
+
302
+ if (match) {
303
+ html = html.replace(match[0], '');
304
+ chars = false;
305
+ }
306
+ // end tag
307
+ } else if (BEGING_END_TAGE_REGEXP.test(html)) {
308
+ match = html.match(END_TAG_REGEXP);
309
+
310
+ if (match) {
311
+ html = html.substring(match[0].length);
312
+ match[0].replace(END_TAG_REGEXP, parseEndTag);
313
+ chars = false;
314
+ }
315
+
316
+ // start tag
317
+ } else if (BEGIN_TAG_REGEXP.test(html)) {
318
+ match = html.match(START_TAG_REGEXP);
319
+
320
+ if (match) {
321
+ // We only have a valid start-tag if there is a '>'.
322
+ if (match[4]) {
323
+ html = html.substring(match[0].length);
324
+ match[0].replace(START_TAG_REGEXP, parseStartTag);
325
+ }
326
+ chars = false;
327
+ } else {
328
+ // no ending tag found --- this piece should be encoded as an entity.
329
+ text += '<';
330
+ html = html.substring(1);
331
+ }
332
+ }
333
+
334
+ if (chars) {
335
+ index = html.indexOf("<");
336
+
337
+ text += index < 0 ? html : html.substring(0, index);
338
+ html = index < 0 ? "" : html.substring(index);
339
+
340
+ if (handler.chars) handler.chars(decodeEntities(text));
341
+ }
342
+
343
+ } else {
344
+ html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
345
+ function(all, text) {
346
+ text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1");
347
+
348
+ if (handler.chars) handler.chars(decodeEntities(text));
349
+
350
+ return "";
351
+ });
352
+
353
+ parseEndTag("", stack.last());
354
+ }
355
+
356
+ if (html == last) {
357
+ throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " +
358
+ "of html: {0}", html);
359
+ }
360
+ last = html;
361
+ }
362
+
363
+ // Clean up any remaining tags
364
+ parseEndTag();
365
+
366
+ function parseStartTag(tag, tagName, rest, unary) {
367
+ tagName = angular.lowercase(tagName);
368
+ if (blockElements[ tagName ]) {
369
+ while (stack.last() && inlineElements[ stack.last() ]) {
370
+ parseEndTag("", stack.last());
371
+ }
372
+ }
373
+
374
+ if (optionalEndTagElements[ tagName ] && stack.last() == tagName) {
375
+ parseEndTag("", tagName);
376
+ }
377
+
378
+ unary = voidElements[ tagName ] || !!unary;
379
+
380
+ if (!unary)
381
+ stack.push(tagName);
382
+
383
+ var attrs = {};
384
+
385
+ rest.replace(ATTR_REGEXP,
386
+ function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
387
+ var value = doubleQuotedValue
388
+ || singleQuotedValue
389
+ || unquotedValue
390
+ || '';
391
+
392
+ attrs[name] = decodeEntities(value);
393
+ });
394
+ if (handler.start) handler.start(tagName, attrs, unary);
395
+ }
396
+
397
+ function parseEndTag(tag, tagName) {
398
+ var pos = 0, i;
399
+ tagName = angular.lowercase(tagName);
400
+ if (tagName)
401
+ // Find the closest opened tag of the same type
402
+ for (pos = stack.length - 1; pos >= 0; pos--)
403
+ if (stack[ pos ] == tagName)
404
+ break;
405
+
406
+ if (pos >= 0) {
407
+ // Close all the open elements, up the stack
408
+ for (i = stack.length - 1; i >= pos; i--)
409
+ if (handler.end) handler.end(stack[ i ]);
410
+
411
+ // Remove the open elements from the stack
412
+ stack.length = pos;
413
+ }
414
+ }
415
+ }
416
+
417
+ var hiddenPre=document.createElement("pre");
418
+ var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
419
+ /**
420
+ * decodes all entities into regular string
421
+ * @param value
422
+ * @returns {string} A string with decoded entities.
423
+ */
424
+ function decodeEntities(value) {
425
+ if (!value) { return ''; }
426
+
427
+ // Note: IE8 does not preserve spaces at the start/end of innerHTML
428
+ // so we must capture them and reattach them afterward
429
+ var parts = spaceRe.exec(value);
430
+ var spaceBefore = parts[1];
431
+ var spaceAfter = parts[3];
432
+ var content = parts[2];
433
+ if (content) {
434
+ hiddenPre.innerHTML=content.replace(/</g,"&lt;");
435
+ // innerText depends on styling as it doesn't display hidden elements.
436
+ // Therefore, it's better to use textContent not to cause unnecessary
437
+ // reflows. However, IE<9 don't support textContent so the innerText
438
+ // fallback is necessary.
439
+ content = 'textContent' in hiddenPre ?
440
+ hiddenPre.textContent : hiddenPre.innerText;
441
+ }
442
+ return spaceBefore + content + spaceAfter;
443
+ }
444
+
445
+ /**
446
+ * Escapes all potentially dangerous characters, so that the
447
+ * resulting string can be safely inserted into attribute or
448
+ * element text.
449
+ * @param value
450
+ * @returns {string} escaped text
451
+ */
452
+ function encodeEntities(value) {
453
+ return value.
454
+ replace(/&/g, '&amp;').
455
+ replace(SURROGATE_PAIR_REGEXP, function(value) {
456
+ var hi = value.charCodeAt(0);
457
+ var low = value.charCodeAt(1);
458
+ return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
459
+ }).
460
+ replace(NON_ALPHANUMERIC_REGEXP, function(value) {
461
+ return '&#' + value.charCodeAt(0) + ';';
462
+ }).
463
+ replace(/</g, '&lt;').
464
+ replace(/>/g, '&gt;');
465
+ }
466
+
467
+ /**
468
+ * create an HTML/XML writer which writes to buffer
469
+ * @param {Array} buf use buf.jain('') to get out sanitized html string
470
+ * @returns {object} in the form of {
471
+ * start: function(tag, attrs, unary) {},
472
+ * end: function(tag) {},
473
+ * chars: function(text) {},
474
+ * comment: function(text) {}
475
+ * }
476
+ */
477
+ function htmlSanitizeWriter(buf, uriValidator) {
478
+ var ignore = false;
479
+ var out = angular.bind(buf, buf.push);
480
+ return {
481
+ start: function(tag, attrs, unary) {
482
+ tag = angular.lowercase(tag);
483
+ if (!ignore && specialElements[tag]) {
484
+ ignore = tag;
485
+ }
486
+ if (!ignore && validElements[tag] === true) {
487
+ out('<');
488
+ out(tag);
489
+ angular.forEach(attrs, function(value, key) {
490
+ var lkey=angular.lowercase(key);
491
+ var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
492
+ if (validAttrs[lkey] === true &&
493
+ (uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
494
+ out(' ');
495
+ out(key);
496
+ out('="');
497
+ out(encodeEntities(value));
498
+ out('"');
499
+ }
500
+ });
501
+ out(unary ? '/>' : '>');
502
+ }
503
+ },
504
+ end: function(tag) {
505
+ tag = angular.lowercase(tag);
506
+ if (!ignore && validElements[tag] === true) {
507
+ out('</');
508
+ out(tag);
509
+ out('>');
510
+ }
511
+ if (tag == ignore) {
512
+ ignore = false;
513
+ }
514
+ },
515
+ chars: function(chars) {
516
+ if (!ignore) {
517
+ out(encodeEntities(chars));
518
+ }
519
+ }
520
+ };
521
+ }
522
+
523
+
524
+ // define ngSanitize module and register $sanitize service
525
+ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
526
+
527
+ /* global sanitizeText: false */
528
+
529
+ /**
530
+ * @ngdoc filter
531
+ * @name linky
532
+ * @kind function
533
+ *
534
+ * @description
535
+ * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
536
+ * plain email address links.
537
+ *
538
+ * Requires the {@link ngSanitize `ngSanitize`} module to be installed.
539
+ *
540
+ * @param {string} text Input text.
541
+ * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
542
+ * @returns {string} Html-linkified text.
543
+ *
544
+ * @usage
545
+ <span ng-bind-html="linky_expression | linky"></span>
546
+ *
547
+ * @example
548
+ <example module="linkyExample" deps="angular-sanitize.js">
549
+ <file name="index.html">
550
+ <script>
551
+ angular.module('linkyExample', ['ngSanitize'])
552
+ .controller('ExampleController', ['$scope', function($scope) {
553
+ $scope.snippet =
554
+ 'Pretty text with some links:\n'+
555
+ 'http://angularjs.org/,\n'+
556
+ 'mailto:us@somewhere.org,\n'+
557
+ 'another@somewhere.org,\n'+
558
+ 'and one more: ftp://127.0.0.1/.';
559
+ $scope.snippetWithTarget = 'http://angularjs.org/';
560
+ }]);
561
+ </script>
562
+ <div ng-controller="ExampleController">
563
+ Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
564
+ <table>
565
+ <tr>
566
+ <td>Filter</td>
567
+ <td>Source</td>
568
+ <td>Rendered</td>
569
+ </tr>
570
+ <tr id="linky-filter">
571
+ <td>linky filter</td>
572
+ <td>
573
+ <pre>&lt;div ng-bind-html="snippet | linky"&gt;<br>&lt;/div&gt;</pre>
574
+ </td>
575
+ <td>
576
+ <div ng-bind-html="snippet | linky"></div>
577
+ </td>
578
+ </tr>
579
+ <tr id="linky-target">
580
+ <td>linky target</td>
581
+ <td>
582
+ <pre>&lt;div ng-bind-html="snippetWithTarget | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
583
+ </td>
584
+ <td>
585
+ <div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
586
+ </td>
587
+ </tr>
588
+ <tr id="escaped-html">
589
+ <td>no filter</td>
590
+ <td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>
591
+ <td><div ng-bind="snippet"></div></td>
592
+ </tr>
593
+ </table>
594
+ </file>
595
+ <file name="protractor.js" type="protractor">
596
+ it('should linkify the snippet with urls', function() {
597
+ expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
598
+ toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' +
599
+ 'another@somewhere.org, and one more: ftp://127.0.0.1/.');
600
+ expect(element.all(by.css('#linky-filter a')).count()).toEqual(4);
601
+ });
602
+
603
+ it('should not linkify snippet without the linky filter', function() {
604
+ expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()).
605
+ toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' +
606
+ 'another@somewhere.org, and one more: ftp://127.0.0.1/.');
607
+ expect(element.all(by.css('#escaped-html a')).count()).toEqual(0);
608
+ });
609
+
610
+ it('should update', function() {
611
+ element(by.model('snippet')).clear();
612
+ element(by.model('snippet')).sendKeys('new http://link.');
613
+ expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
614
+ toBe('new http://link.');
615
+ expect(element.all(by.css('#linky-filter a')).count()).toEqual(1);
616
+ expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText())
617
+ .toBe('new http://link.');
618
+ });
619
+
620
+ it('should work with the target property', function() {
621
+ expect(element(by.id('linky-target')).
622
+ element(by.binding("snippetWithTarget | linky:'_blank'")).getText()).
623
+ toBe('http://angularjs.org/');
624
+ expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
625
+ });
626
+ </file>
627
+ </example>
628
+ */
629
+ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
630
+ var LINKY_URL_REGEXP =
631
+ /((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"”’]/,
632
+ MAILTO_REGEXP = /^mailto:/;
633
+
634
+ return function(text, target) {
635
+ if (!text) return text;
636
+ var match;
637
+ var raw = text;
638
+ var html = [];
639
+ var url;
640
+ var i;
641
+ while ((match = raw.match(LINKY_URL_REGEXP))) {
642
+ // We can not end in these as they are sometimes found at the end of the sentence
643
+ url = match[0];
644
+ // if we did not match ftp/http/www/mailto then assume mailto
645
+ if (!match[2] && !match[4]) {
646
+ url = (match[3] ? 'http://' : 'mailto:') + url;
647
+ }
648
+ i = match.index;
649
+ addText(raw.substr(0, i));
650
+ addLink(url, match[0].replace(MAILTO_REGEXP, ''));
651
+ raw = raw.substring(i + match[0].length);
652
+ }
653
+ addText(raw);
654
+ return $sanitize(html.join(''));
655
+
656
+ function addText(text) {
657
+ if (!text) {
658
+ return;
659
+ }
660
+ html.push(sanitizeText(text));
661
+ }
662
+
663
+ function addLink(url, text) {
664
+ html.push('<a ');
665
+ if (angular.isDefined(target)) {
666
+ html.push('target="',
667
+ target,
668
+ '" ');
669
+ }
670
+ html.push('href="',
671
+ url.replace(/"/g, '&quot;'),
672
+ '">');
673
+ addText(text);
674
+ html.push('</a>');
675
+ }
676
+ };
677
+ }]);
678
+
679
+
680
+ })(window, window.angular);