rails-angularjs 1.3.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +22 -0
  3. data/README.md +37 -0
  4. data/lib/rails-angularjs.rb +9 -0
  5. data/lib/rails-angularjs/engine.rb +4 -0
  6. data/lib/rails-angularjs/sprockets.rb +3 -0
  7. data/lib/rails-angularjs/version.rb +4 -0
  8. data/vendor/assets/javascripts/angularjs/angular-animate.js +2137 -0
  9. data/vendor/assets/javascripts/angularjs/angular-animate.min.js +33 -0
  10. data/vendor/assets/javascripts/angularjs/angular-aria.js +339 -0
  11. data/vendor/assets/javascripts/angularjs/angular-aria.min.js +12 -0
  12. data/vendor/assets/javascripts/angularjs/angular-cookies.js +206 -0
  13. data/vendor/assets/javascripts/angularjs/angular-cookies.min.js +8 -0
  14. data/vendor/assets/javascripts/angularjs/angular-loader.js +405 -0
  15. data/vendor/assets/javascripts/angularjs/angular-loader.min.js +9 -0
  16. data/vendor/assets/javascripts/angularjs/angular-messages.js +400 -0
  17. data/vendor/assets/javascripts/angularjs/angular-messages.min.js +10 -0
  18. data/vendor/assets/javascripts/angularjs/angular-mocks.js +2396 -0
  19. data/vendor/assets/javascripts/angularjs/angular-resource.js +667 -0
  20. data/vendor/assets/javascripts/angularjs/angular-resource.min.js +13 -0
  21. data/vendor/assets/javascripts/angularjs/angular-route.js +989 -0
  22. data/vendor/assets/javascripts/angularjs/angular-route.min.js +15 -0
  23. data/vendor/assets/javascripts/angularjs/angular-sanitize.js +680 -0
  24. data/vendor/assets/javascripts/angularjs/angular-sanitize.min.js +16 -0
  25. data/vendor/assets/javascripts/angularjs/angular-scenario.js +37499 -0
  26. data/vendor/assets/javascripts/angularjs/angular-touch.js +622 -0
  27. data/vendor/assets/javascripts/angularjs/angular-touch.min.js +13 -0
  28. data/vendor/assets/javascripts/angularjs/angular.js +26130 -0
  29. data/vendor/assets/javascripts/angularjs/angular.min.js +250 -0
  30. data/vendor/assets/javascripts/angularjs/unstable/angular-animate.js +2137 -0
  31. data/vendor/assets/javascripts/angularjs/unstable/angular-animate.min.js +33 -0
  32. data/vendor/assets/javascripts/angularjs/unstable/angular-aria.js +339 -0
  33. data/vendor/assets/javascripts/angularjs/unstable/angular-aria.min.js +12 -0
  34. data/vendor/assets/javascripts/angularjs/unstable/angular-cookies.js +206 -0
  35. data/vendor/assets/javascripts/angularjs/unstable/angular-cookies.min.js +8 -0
  36. data/vendor/assets/javascripts/angularjs/unstable/angular-loader.js +410 -0
  37. data/vendor/assets/javascripts/angularjs/unstable/angular-loader.min.js +9 -0
  38. data/vendor/assets/javascripts/angularjs/unstable/angular-messages.js +400 -0
  39. data/vendor/assets/javascripts/angularjs/unstable/angular-messages.min.js +10 -0
  40. data/vendor/assets/javascripts/angularjs/unstable/angular-mocks.js +2400 -0
  41. data/vendor/assets/javascripts/angularjs/unstable/angular-resource.js +667 -0
  42. data/vendor/assets/javascripts/angularjs/unstable/angular-resource.min.js +13 -0
  43. data/vendor/assets/javascripts/angularjs/unstable/angular-route.js +989 -0
  44. data/vendor/assets/javascripts/angularjs/unstable/angular-route.min.js +15 -0
  45. data/vendor/assets/javascripts/angularjs/unstable/angular-sanitize.js +682 -0
  46. data/vendor/assets/javascripts/angularjs/unstable/angular-sanitize.min.js +16 -0
  47. data/vendor/assets/javascripts/angularjs/unstable/angular-scenario.js +38463 -0
  48. data/vendor/assets/javascripts/angularjs/unstable/angular-touch.js +622 -0
  49. data/vendor/assets/javascripts/angularjs/unstable/angular-touch.min.js +13 -0
  50. data/vendor/assets/javascripts/angularjs/unstable/angular.js +27088 -0
  51. data/vendor/assets/javascripts/angularjs/unstable/angular.min.js +281 -0
  52. metadata +140 -0
@@ -0,0 +1,15 @@
1
+ /*
2
+ AngularJS v1.3.12
3
+ (c) 2010-2014 Google, Inc. http://angularjs.org
4
+ License: MIT
5
+ */
6
+ (function(q,d,C){'use strict';function v(r,k,h){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,f,b,c,y){function z(){l&&(h.cancel(l),l=null);m&&(m.$destroy(),m=null);n&&(l=h.leave(n),l.then(function(){l=null}),n=null)}function x(){var b=r.current&&r.current.locals;if(d.isDefined(b&&b.$template)){var b=a.$new(),c=r.current;n=y(b,function(b){h.enter(b,null,n||f).then(function(){!d.isDefined(t)||t&&!a.$eval(t)||k()});z()});m=c.scope=b;m.$emit("$viewContentLoaded");
7
+ m.$eval(w)}else z()}var m,n,l,t=b.autoscroll,w=b.onload||"";a.$on("$routeChangeSuccess",x);x()}}}function A(d,k,h){return{restrict:"ECA",priority:-400,link:function(a,f){var b=h.current,c=b.locals;f.html(c.$template);var y=d(f.contents());b.controller&&(c.$scope=a,c=k(b.controller,c),b.controllerAs&&(a[b.controllerAs]=c),f.data("$ngControllerController",c),f.children().data("$ngControllerController",c));y(a)}}}q=d.module("ngRoute",["ng"]).provider("$route",function(){function r(a,f){return d.extend(Object.create(a),
8
+ f)}function k(a,d){var b=d.caseInsensitiveMatch,c={originalPath:a,regexp:a},h=c.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,d,b,c){a="?"===c?c:null;c="*"===c?c:null;h.push({name:b,optional:!!a});d=d||"";return""+(a?"":d)+"(?:"+(a?d:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");c.regexp=new RegExp("^"+a+"$",b?"i":"");return c}var h={};this.when=function(a,f){var b=d.copy(f);d.isUndefined(b.reloadOnSearch)&&(b.reloadOnSearch=!0);
9
+ d.isUndefined(b.caseInsensitiveMatch)&&(b.caseInsensitiveMatch=this.caseInsensitiveMatch);h[a]=d.extend(b,a&&k(a,b));if(a){var c="/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";h[c]=d.extend({redirectTo:a},k(c,b))}return this};this.caseInsensitiveMatch=!1;this.otherwise=function(a){"string"===typeof a&&(a={redirectTo:a});this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$templateRequest","$sce",function(a,f,b,c,k,q,x){function m(b){var e=s.current;
10
+ (v=(p=l())&&e&&p.$$route===e.$$route&&d.equals(p.pathParams,e.pathParams)&&!p.reloadOnSearch&&!w)||!e&&!p||a.$broadcast("$routeChangeStart",p,e).defaultPrevented&&b&&b.preventDefault()}function n(){var u=s.current,e=p;if(v)u.params=e.params,d.copy(u.params,b),a.$broadcast("$routeUpdate",u);else if(e||u)w=!1,(s.current=e)&&e.redirectTo&&(d.isString(e.redirectTo)?f.path(t(e.redirectTo,e.params)).search(e.params).replace():f.url(e.redirectTo(e.pathParams,f.path(),f.search())).replace()),c.when(e).then(function(){if(e){var a=
11
+ d.extend({},e.resolve),b,g;d.forEach(a,function(b,e){a[e]=d.isString(b)?k.get(b):k.invoke(b,null,null,e)});d.isDefined(b=e.template)?d.isFunction(b)&&(b=b(e.params)):d.isDefined(g=e.templateUrl)&&(d.isFunction(g)&&(g=g(e.params)),g=x.getTrustedResourceUrl(g),d.isDefined(g)&&(e.loadedTemplateUrl=g,b=q(g)));d.isDefined(b)&&(a.$template=b);return c.all(a)}}).then(function(c){e==s.current&&(e&&(e.locals=c,d.copy(e.params,b)),a.$broadcast("$routeChangeSuccess",e,u))},function(b){e==s.current&&a.$broadcast("$routeChangeError",
12
+ e,u,b)})}function l(){var a,b;d.forEach(h,function(c,h){var g;if(g=!b){var k=f.path();g=c.keys;var m={};if(c.regexp)if(k=c.regexp.exec(k)){for(var l=1,n=k.length;l<n;++l){var p=g[l-1],q=k[l];p&&q&&(m[p.name]=q)}g=m}else g=null;else g=null;g=a=g}g&&(b=r(c,{params:d.extend({},f.search(),a),pathParams:a}),b.$$route=c)});return b||h[null]&&r(h[null],{params:{},pathParams:{}})}function t(a,b){var c=[];d.forEach((a||"").split(":"),function(a,d){if(0===d)c.push(a);else{var f=a.match(/(\w+)(?:[?*])?(.*)/),
13
+ h=f[1];c.push(b[h]);c.push(f[2]||"");delete b[h]}});return c.join("")}var w=!1,p,v,s={routes:h,reload:function(){w=!0;a.$evalAsync(function(){m();n()})},updateParams:function(a){if(this.current&&this.current.$$route)a=d.extend({},this.current.params,a),f.path(t(this.current.$$route.originalPath,a)),f.search(a);else throw B("norout");}};a.$on("$locationChangeStart",m);a.$on("$locationChangeSuccess",n);return s}]});var B=d.$$minErr("ngRoute");q.provider("$routeParams",function(){this.$get=function(){return{}}});
14
+ q.directive("ngView",v);q.directive("ngView",A);v.$inject=["$route","$anchorScroll","$animate"];A.$inject=["$compile","$controller","$route"]})(window,window.angular);
15
+ //# sourceMappingURL=angular-route.min.js.map
@@ -0,0 +1,680 @@
1
+ /**
2
+ * @license AngularJS v1.3.12
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);