material_raingular 0.0.7 → 0.1.0
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/.gitignore +1 -0
- data/lib/material_raingular/version.rb +1 -1
- data/vendor/assets/angular/angular-animate.js +1027 -620
- data/vendor/assets/angular/angular-aria.js +111 -91
- data/vendor/assets/angular/angular-cookies.js +17 -15
- data/vendor/assets/angular/angular-loader.js +70 -15
- data/vendor/assets/angular/angular-material.min.css +2 -2
- data/vendor/assets/angular/angular-material.min.js +10 -9
- data/vendor/assets/angular/angular-message-format.js +2 -2
- data/vendor/assets/angular/angular-messages.js +77 -33
- data/vendor/assets/angular/angular-resource.js +140 -40
- data/vendor/assets/angular/angular-route.js +52 -21
- data/vendor/assets/angular/angular-sanitize.js +283 -249
- data/vendor/assets/angular/angular-touch.js +117 -15
- data/vendor/assets/angular/angular.js +4528 -1947
- metadata +2 -2
@@ -1,6 +1,6 @@
|
|
1
1
|
/**
|
2
|
-
* @license AngularJS v1.
|
3
|
-
* (c) 2010-
|
2
|
+
* @license AngularJS v1.5.3
|
3
|
+
* (c) 2010-2016 Google, Inc. http://angularjs.org
|
4
4
|
* License: MIT
|
5
5
|
*/
|
6
6
|
(function(window, angular, undefined) {'use strict';
|
@@ -22,7 +22,11 @@
|
|
22
22
|
*/
|
23
23
|
/* global -ngRouteModule */
|
24
24
|
var ngRouteModule = angular.module('ngRoute', ['ng']).
|
25
|
-
provider('$route', $RouteProvider)
|
25
|
+
provider('$route', $RouteProvider).
|
26
|
+
// Ensure `$route` will be instantiated in time to capture the initial
|
27
|
+
// `$locationChangeSuccess` event. This is necessary in case `ngView` is
|
28
|
+
// included in an asynchronously loaded template.
|
29
|
+
run(['$route', angular.noop]),
|
26
30
|
$routeMinErr = angular.$$minErr('ngRoute');
|
27
31
|
|
28
32
|
/**
|
@@ -105,8 +109,17 @@ function $RouteProvider() {
|
|
105
109
|
* If all the promises are resolved successfully, the values of the resolved promises are
|
106
110
|
* injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is
|
107
111
|
* fired. If any of the promises are rejected the
|
108
|
-
* {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired.
|
109
|
-
*
|
112
|
+
* {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired.
|
113
|
+
* For easier access to the resolved dependencies from the template, the `resolve` map will
|
114
|
+
* be available on the scope of the route, under `$resolve` (by default) or a custom name
|
115
|
+
* specified by the `resolveAs` property (see below). This can be particularly useful, when
|
116
|
+
* working with {@link angular.Module#component components} as route templates.<br />
|
117
|
+
* <div class="alert alert-warning">
|
118
|
+
* **Note:** If your scope already contains a property with this name, it will be hidden
|
119
|
+
* or overwritten. Make sure, you specify an appropriate name for this property, that
|
120
|
+
* does not collide with other properties on the scope.
|
121
|
+
* </div>
|
122
|
+
* The map object is:
|
110
123
|
*
|
111
124
|
* - `key` – `{string}`: a name of a dependency to be injected into the controller.
|
112
125
|
* - `factory` - `{string|function}`: If `string` then it is an alias for a service.
|
@@ -116,7 +129,10 @@ function $RouteProvider() {
|
|
116
129
|
* `ngRoute.$routeParams` will still refer to the previous route within these resolve
|
117
130
|
* functions. Use `$route.current.params` to access the new route parameters, instead.
|
118
131
|
*
|
119
|
-
* - `
|
132
|
+
* - `resolveAs` - `{string=}` - The name under which the `resolve` map will be available on
|
133
|
+
* the scope of the route. If omitted, defaults to `$resolve`.
|
134
|
+
*
|
135
|
+
* - `redirectTo` – `{(string|function())=}` – value to update
|
120
136
|
* {@link ng.$location $location} path with and trigger route redirection.
|
121
137
|
*
|
122
138
|
* If `redirectTo` is a function, it will be called with the following parameters:
|
@@ -129,13 +145,13 @@ function $RouteProvider() {
|
|
129
145
|
* The custom `redirectTo` function is expected to return a string which will be used
|
130
146
|
* to update `$location.path()` and `$location.search()`.
|
131
147
|
*
|
132
|
-
* - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()`
|
148
|
+
* - `[reloadOnSearch=true]` - `{boolean=}` - reload route when only `$location.search()`
|
133
149
|
* or `$location.hash()` changes.
|
134
150
|
*
|
135
151
|
* If the option is set to `false` and url in the browser changes, then
|
136
152
|
* `$routeUpdate` event is broadcasted on the root scope.
|
137
153
|
*
|
138
|
-
* - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive
|
154
|
+
* - `[caseInsensitiveMatch=false]` - `{boolean=}` - match routes without being case sensitive
|
139
155
|
*
|
140
156
|
* If the option is set to `true`, then the particular route can be matched without being
|
141
157
|
* case sensitive
|
@@ -206,9 +222,9 @@ function $RouteProvider() {
|
|
206
222
|
|
207
223
|
path = path
|
208
224
|
.replace(/([().])/g, '\\$1')
|
209
|
-
.replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option) {
|
210
|
-
var optional = option === '?'
|
211
|
-
var star = option === '*'
|
225
|
+
.replace(/(\/)?:(\w+)(\*\?|[\?\*])?/g, function(_, slash, key, option) {
|
226
|
+
var optional = (option === '?' || option === '*?') ? '?' : null;
|
227
|
+
var star = (option === '*' || option === '*?') ? '*' : null;
|
212
228
|
keys.push({ name: key, optional: !!optional });
|
213
229
|
slash = slash || '';
|
214
230
|
return ''
|
@@ -265,7 +281,7 @@ function $RouteProvider() {
|
|
265
281
|
* @property {Object} current Reference to the current route definition.
|
266
282
|
* The route definition contains:
|
267
283
|
*
|
268
|
-
* - `controller`: The controller constructor as
|
284
|
+
* - `controller`: The controller constructor as defined in the route definition.
|
269
285
|
* - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for
|
270
286
|
* controller instantiation. The `locals` contain
|
271
287
|
* the resolved values of the `resolve` map. Additionally the `locals` also contain:
|
@@ -273,6 +289,10 @@ function $RouteProvider() {
|
|
273
289
|
* - `$scope` - The current route scope.
|
274
290
|
* - `$template` - The current route template HTML.
|
275
291
|
*
|
292
|
+
* The `locals` will be assigned to the route scope's `$resolve` property. You can override
|
293
|
+
* the property name, using `resolveAs` in the route definition. See
|
294
|
+
* {@link ngRoute.$routeProvider $routeProvider} for more info.
|
295
|
+
*
|
276
296
|
* @property {Object} routes Object with all route configuration Objects as its properties.
|
277
297
|
*
|
278
298
|
* @description
|
@@ -412,7 +432,9 @@ function $RouteProvider() {
|
|
412
432
|
* @name $route#$routeChangeSuccess
|
413
433
|
* @eventType broadcast on root scope
|
414
434
|
* @description
|
415
|
-
* Broadcasted after a route
|
435
|
+
* Broadcasted after a route change has happened successfully.
|
436
|
+
* The `resolve` dependencies are now available in the `current.locals` property.
|
437
|
+
*
|
416
438
|
* {@link ngRoute.directive:ngView ngView} listens for the directive
|
417
439
|
* to instantiate the controller and render the view.
|
418
440
|
*
|
@@ -466,10 +488,18 @@ function $RouteProvider() {
|
|
466
488
|
*/
|
467
489
|
reload: function() {
|
468
490
|
forceReload = true;
|
491
|
+
|
492
|
+
var fakeLocationEvent = {
|
493
|
+
defaultPrevented: false,
|
494
|
+
preventDefault: function fakePreventDefault() {
|
495
|
+
this.defaultPrevented = true;
|
496
|
+
forceReload = false;
|
497
|
+
}
|
498
|
+
};
|
499
|
+
|
469
500
|
$rootScope.$evalAsync(function() {
|
470
|
-
|
471
|
-
|
472
|
-
commitRoute();
|
501
|
+
prepareRoute(fakeLocationEvent);
|
502
|
+
if (!fakeLocationEvent.defaultPrevented) commitRoute();
|
473
503
|
});
|
474
504
|
},
|
475
505
|
|
@@ -596,9 +626,8 @@ function $RouteProvider() {
|
|
596
626
|
if (angular.isFunction(templateUrl)) {
|
597
627
|
templateUrl = templateUrl(nextRoute.params);
|
598
628
|
}
|
599
|
-
templateUrl = $sce.getTrustedResourceUrl(templateUrl);
|
600
629
|
if (angular.isDefined(templateUrl)) {
|
601
|
-
nextRoute.loadedTemplateUrl = templateUrl;
|
630
|
+
nextRoute.loadedTemplateUrl = $sce.valueOf(templateUrl);
|
602
631
|
template = $templateRequest(templateUrl);
|
603
632
|
}
|
604
633
|
}
|
@@ -724,8 +753,10 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
|
|
724
753
|
* Requires the {@link ngRoute `ngRoute`} module to be installed.
|
725
754
|
*
|
726
755
|
* @animations
|
727
|
-
*
|
728
|
-
*
|
756
|
+
* | Animation | Occurs |
|
757
|
+
* |----------------------------------|-------------------------------------|
|
758
|
+
* | {@link ng.$animate#enter enter} | when the new element is inserted to the DOM |
|
759
|
+
* | {@link ng.$animate#leave leave} | when the old element is removed from to the DOM |
|
729
760
|
*
|
730
761
|
* The enter and leave animation occur concurrently.
|
731
762
|
*
|
@@ -795,7 +826,6 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
|
|
795
826
|
}
|
796
827
|
|
797
828
|
.view-animate.ng-enter, .view-animate.ng-leave {
|
798
|
-
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
|
799
829
|
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
|
800
830
|
|
801
831
|
display:block;
|
@@ -981,6 +1011,7 @@ function ngViewFillContentFactory($compile, $controller, $route) {
|
|
981
1011
|
$element.data('$ngControllerController', controller);
|
982
1012
|
$element.children().data('$ngControllerController', controller);
|
983
1013
|
}
|
1014
|
+
scope[current.resolveAs || '$resolve'] = locals;
|
984
1015
|
|
985
1016
|
link(scope);
|
986
1017
|
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
/**
|
2
|
-
* @license AngularJS v1.
|
3
|
-
* (c) 2010-
|
2
|
+
* @license AngularJS v1.5.3
|
3
|
+
* (c) 2010-2016 Google, Inc. http://angularjs.org
|
4
4
|
* License: MIT
|
5
5
|
*/
|
6
6
|
(function(window, angular, undefined) {'use strict';
|
@@ -33,36 +33,23 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
|
|
33
33
|
* See {@link ngSanitize.$sanitize `$sanitize`} for usage.
|
34
34
|
*/
|
35
35
|
|
36
|
-
/*
|
37
|
-
* HTML Parser By Misko Hevery (misko@hevery.com)
|
38
|
-
* based on: HTML Parser By John Resig (ejohn.org)
|
39
|
-
* Original code by Erik Arvidsson, Mozilla Public License
|
40
|
-
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
41
|
-
*
|
42
|
-
* // Use like so:
|
43
|
-
* htmlParser(htmlString, {
|
44
|
-
* start: function(tag, attrs, unary) {},
|
45
|
-
* end: function(tag) {},
|
46
|
-
* chars: function(text) {},
|
47
|
-
* comment: function(text) {}
|
48
|
-
* });
|
49
|
-
*
|
50
|
-
*/
|
51
|
-
|
52
|
-
|
53
36
|
/**
|
54
37
|
* @ngdoc service
|
55
38
|
* @name $sanitize
|
56
39
|
* @kind function
|
57
40
|
*
|
58
41
|
* @description
|
42
|
+
* Sanitizes an html string by stripping all potentially dangerous tokens.
|
43
|
+
*
|
59
44
|
* The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are
|
60
45
|
* then serialized back to properly escaped html string. This means that no unsafe input can make
|
61
|
-
* it into the returned string
|
62
|
-
*
|
63
|
-
*
|
64
|
-
*
|
65
|
-
*
|
46
|
+
* it into the returned string.
|
47
|
+
*
|
48
|
+
* The whitelist for URL sanitization of attribute values is configured using the functions
|
49
|
+
* `aHrefSanitizationWhitelist` and `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider
|
50
|
+
* `$compileProvider`}.
|
51
|
+
*
|
52
|
+
* The input may also contain SVG markup if this is enabled via {@link $sanitizeProvider}.
|
66
53
|
*
|
67
54
|
* @param {string} html HTML input.
|
68
55
|
* @returns {string} Sanitized HTML.
|
@@ -148,16 +135,70 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
|
|
148
135
|
</file>
|
149
136
|
</example>
|
150
137
|
*/
|
138
|
+
|
139
|
+
|
140
|
+
/**
|
141
|
+
* @ngdoc provider
|
142
|
+
* @name $sanitizeProvider
|
143
|
+
*
|
144
|
+
* @description
|
145
|
+
* Creates and configures {@link $sanitize} instance.
|
146
|
+
*/
|
151
147
|
function $SanitizeProvider() {
|
148
|
+
var svgEnabled = false;
|
149
|
+
|
152
150
|
this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
|
151
|
+
if (svgEnabled) {
|
152
|
+
angular.extend(validElements, svgElements);
|
153
|
+
}
|
153
154
|
return function(html) {
|
154
155
|
var buf = [];
|
155
156
|
htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
|
156
|
-
return !/^unsafe
|
157
|
+
return !/^unsafe:/.test($$sanitizeUri(uri, isImage));
|
157
158
|
}));
|
158
159
|
return buf.join('');
|
159
160
|
};
|
160
161
|
}];
|
162
|
+
|
163
|
+
|
164
|
+
/**
|
165
|
+
* @ngdoc method
|
166
|
+
* @name $sanitizeProvider#enableSvg
|
167
|
+
* @kind function
|
168
|
+
*
|
169
|
+
* @description
|
170
|
+
* Enables a subset of svg to be supported by the sanitizer.
|
171
|
+
*
|
172
|
+
* <div class="alert alert-warning">
|
173
|
+
* <p>By enabling this setting without taking other precautions, you might expose your
|
174
|
+
* application to click-hijacking attacks. In these attacks, sanitized svg elements could be positioned
|
175
|
+
* outside of the containing element and be rendered over other elements on the page (e.g. a login
|
176
|
+
* link). Such behavior can then result in phishing incidents.</p>
|
177
|
+
*
|
178
|
+
* <p>To protect against these, explicitly setup `overflow: hidden` css rule for all potential svg
|
179
|
+
* tags within the sanitized content:</p>
|
180
|
+
*
|
181
|
+
* <br>
|
182
|
+
*
|
183
|
+
* <pre><code>
|
184
|
+
* .rootOfTheIncludedContent svg {
|
185
|
+
* overflow: hidden !important;
|
186
|
+
* }
|
187
|
+
* </code></pre>
|
188
|
+
* </div>
|
189
|
+
*
|
190
|
+
* @param {boolean=} regexp New regexp to whitelist urls with.
|
191
|
+
* @returns {boolean|ng.$sanitizeProvider} Returns the currently configured value if called
|
192
|
+
* without an argument or self for chaining otherwise.
|
193
|
+
*/
|
194
|
+
this.enableSvg = function(enableSvg) {
|
195
|
+
if (angular.isDefined(enableSvg)) {
|
196
|
+
svgEnabled = enableSvg;
|
197
|
+
return this;
|
198
|
+
} else {
|
199
|
+
return svgEnabled;
|
200
|
+
}
|
201
|
+
};
|
161
202
|
}
|
162
203
|
|
163
204
|
function sanitizeText(chars) {
|
@@ -169,18 +210,9 @@ function sanitizeText(chars) {
|
|
169
210
|
|
170
211
|
|
171
212
|
// Regular Expressions for parsing tags and attributes
|
172
|
-
var
|
173
|
-
/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,
|
174
|
-
END_TAG_REGEXP = /^<\/\s*([\w:-]+)[^>]*>/,
|
175
|
-
ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
|
176
|
-
BEGIN_TAG_REGEXP = /^</,
|
177
|
-
BEGING_END_TAGE_REGEXP = /^<\//,
|
178
|
-
COMMENT_REGEXP = /<!--(.*?)-->/g,
|
179
|
-
DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i,
|
180
|
-
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
|
181
|
-
SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
|
213
|
+
var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
|
182
214
|
// Match everything outside of normal chars and " (quote character)
|
183
|
-
NON_ALPHANUMERIC_REGEXP = /([
|
215
|
+
NON_ALPHANUMERIC_REGEXP = /([^\#-~ |!])/g;
|
184
216
|
|
185
217
|
|
186
218
|
// Good source of info about elements and attributes
|
@@ -189,23 +221,23 @@ var START_TAG_REGEXP =
|
|
189
221
|
|
190
222
|
// Safe Void Elements - HTML5
|
191
223
|
// http://dev.w3.org/html5/spec/Overview.html#void-elements
|
192
|
-
var voidElements =
|
224
|
+
var voidElements = toMap("area,br,col,hr,img,wbr");
|
193
225
|
|
194
226
|
// Elements that you can, intentionally, leave open (and which close themselves)
|
195
227
|
// http://dev.w3.org/html5/spec/Overview.html#optional-tags
|
196
|
-
var optionalEndTagBlockElements =
|
197
|
-
optionalEndTagInlineElements =
|
228
|
+
var optionalEndTagBlockElements = toMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
|
229
|
+
optionalEndTagInlineElements = toMap("rp,rt"),
|
198
230
|
optionalEndTagElements = angular.extend({},
|
199
231
|
optionalEndTagInlineElements,
|
200
232
|
optionalEndTagBlockElements);
|
201
233
|
|
202
234
|
// Safe Block Elements - HTML5
|
203
|
-
var blockElements = angular.extend({}, optionalEndTagBlockElements,
|
235
|
+
var blockElements = angular.extend({}, optionalEndTagBlockElements, toMap("address,article," +
|
204
236
|
"aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," +
|
205
|
-
"h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,
|
237
|
+
"h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul"));
|
206
238
|
|
207
239
|
// Inline Elements - HTML5
|
208
|
-
var inlineElements = angular.extend({}, optionalEndTagInlineElements,
|
240
|
+
var inlineElements = angular.extend({}, optionalEndTagInlineElements, toMap("a,abbr,acronym,b," +
|
209
241
|
"bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +
|
210
242
|
"samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
|
211
243
|
|
@@ -213,32 +245,31 @@ var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a
|
|
213
245
|
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements
|
214
246
|
// Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted.
|
215
247
|
// They can potentially allow for arbitrary javascript to be executed. See #11290
|
216
|
-
var svgElements =
|
248
|
+
var svgElements = toMap("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph," +
|
217
249
|
"hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline," +
|
218
|
-
"radialGradient,rect,stop,svg,switch,text,title,tspan
|
250
|
+
"radialGradient,rect,stop,svg,switch,text,title,tspan");
|
219
251
|
|
220
|
-
//
|
221
|
-
var
|
252
|
+
// Blocked Elements (will be stripped)
|
253
|
+
var blockedElements = toMap("script,style");
|
222
254
|
|
223
255
|
var validElements = angular.extend({},
|
224
256
|
voidElements,
|
225
257
|
blockElements,
|
226
258
|
inlineElements,
|
227
|
-
optionalEndTagElements
|
228
|
-
svgElements);
|
259
|
+
optionalEndTagElements);
|
229
260
|
|
230
261
|
//Attributes that have href and hence need to be sanitized
|
231
|
-
var uriAttrs =
|
262
|
+
var uriAttrs = toMap("background,cite,href,longdesc,src,xlink:href");
|
232
263
|
|
233
|
-
var htmlAttrs =
|
264
|
+
var htmlAttrs = toMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
|
234
265
|
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
|
235
266
|
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
|
236
|
-
'scope,scrolling,shape,size,span,start,summary,target,title,type,' +
|
267
|
+
'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' +
|
237
268
|
'valign,value,vspace,width');
|
238
269
|
|
239
270
|
// SVG attributes (without "id" and "name" attributes)
|
240
271
|
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes
|
241
|
-
var svgAttrs =
|
272
|
+
var svgAttrs = toMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
|
242
273
|
'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' +
|
243
274
|
'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' +
|
244
275
|
'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' +
|
@@ -259,7 +290,7 @@ var validAttrs = angular.extend({},
|
|
259
290
|
svgAttrs,
|
260
291
|
htmlAttrs);
|
261
292
|
|
262
|
-
function
|
293
|
+
function toMap(str, lowercaseKeys) {
|
263
294
|
var obj = {}, items = str.split(','), i;
|
264
295
|
for (i = 0; i < items.length; i++) {
|
265
296
|
obj[lowercaseKeys ? angular.lowercase(items[i]) : items[i]] = true;
|
@@ -267,11 +298,32 @@ function makeMap(str, lowercaseKeys) {
|
|
267
298
|
return obj;
|
268
299
|
}
|
269
300
|
|
301
|
+
var inertBodyElement;
|
302
|
+
(function(window) {
|
303
|
+
var doc;
|
304
|
+
if (window.document && window.document.implementation) {
|
305
|
+
doc = window.document.implementation.createHTMLDocument("inert");
|
306
|
+
} else {
|
307
|
+
throw $sanitizeMinErr('noinert', "Can't create an inert html document");
|
308
|
+
}
|
309
|
+
var docElement = doc.documentElement || doc.getDocumentElement();
|
310
|
+
var bodyElements = docElement.getElementsByTagName('body');
|
311
|
+
|
312
|
+
// usually there should be only one body element in the document, but IE doesn't have any, so we need to create one
|
313
|
+
if (bodyElements.length === 1) {
|
314
|
+
inertBodyElement = bodyElements[0];
|
315
|
+
} else {
|
316
|
+
var html = doc.createElement('html');
|
317
|
+
inertBodyElement = doc.createElement('body');
|
318
|
+
html.appendChild(inertBodyElement);
|
319
|
+
doc.appendChild(html);
|
320
|
+
}
|
321
|
+
})(window);
|
270
322
|
|
271
323
|
/**
|
272
324
|
* @example
|
273
325
|
* htmlParser(htmlString, {
|
274
|
-
* start: function(tag, attrs
|
326
|
+
* start: function(tag, attrs) {},
|
275
327
|
* end: function(tag) {},
|
276
328
|
* chars: function(text) {},
|
277
329
|
* comment: function(text) {}
|
@@ -281,169 +333,74 @@ function makeMap(str, lowercaseKeys) {
|
|
281
333
|
* @param {object} handler
|
282
334
|
*/
|
283
335
|
function htmlParser(html, handler) {
|
284
|
-
if (
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
html = '' + html;
|
289
|
-
}
|
336
|
+
if (html === null || html === undefined) {
|
337
|
+
html = '';
|
338
|
+
} else if (typeof html !== 'string') {
|
339
|
+
html = '' + html;
|
290
340
|
}
|
291
|
-
|
292
|
-
stack.last = function() { return stack[stack.length - 1]; };
|
341
|
+
inertBodyElement.innerHTML = html;
|
293
342
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
// Comment
|
302
|
-
if (html.indexOf("<!--") === 0) {
|
303
|
-
// comments containing -- are not allowed unless they terminate the comment
|
304
|
-
index = html.indexOf("--", 4);
|
305
|
-
|
306
|
-
if (index >= 0 && html.lastIndexOf("-->", index) === index) {
|
307
|
-
if (handler.comment) handler.comment(html.substring(4, index));
|
308
|
-
html = html.substring(index + 3);
|
309
|
-
chars = false;
|
310
|
-
}
|
311
|
-
// DOCTYPE
|
312
|
-
} else if (DOCTYPE_REGEXP.test(html)) {
|
313
|
-
match = html.match(DOCTYPE_REGEXP);
|
314
|
-
|
315
|
-
if (match) {
|
316
|
-
html = html.replace(match[0], '');
|
317
|
-
chars = false;
|
318
|
-
}
|
319
|
-
// end tag
|
320
|
-
} else if (BEGING_END_TAGE_REGEXP.test(html)) {
|
321
|
-
match = html.match(END_TAG_REGEXP);
|
322
|
-
|
323
|
-
if (match) {
|
324
|
-
html = html.substring(match[0].length);
|
325
|
-
match[0].replace(END_TAG_REGEXP, parseEndTag);
|
326
|
-
chars = false;
|
327
|
-
}
|
343
|
+
//mXSS protection
|
344
|
+
var mXSSAttempts = 5;
|
345
|
+
do {
|
346
|
+
if (mXSSAttempts === 0) {
|
347
|
+
throw $sanitizeMinErr('uinput', "Failed to sanitize html because the input is unstable");
|
348
|
+
}
|
349
|
+
mXSSAttempts--;
|
328
350
|
|
329
|
-
|
330
|
-
|
331
|
-
|
351
|
+
// strip custom-namespaced attributes on IE<=11
|
352
|
+
if (document.documentMode <= 11) {
|
353
|
+
stripCustomNsAttrs(inertBodyElement);
|
354
|
+
}
|
355
|
+
html = inertBodyElement.innerHTML; //trigger mXSS
|
356
|
+
inertBodyElement.innerHTML = html;
|
357
|
+
} while (html !== inertBodyElement.innerHTML);
|
358
|
+
|
359
|
+
var node = inertBodyElement.firstChild;
|
360
|
+
while (node) {
|
361
|
+
switch (node.nodeType) {
|
362
|
+
case 1: // ELEMENT_NODE
|
363
|
+
handler.start(node.nodeName.toLowerCase(), attrToMap(node.attributes));
|
364
|
+
break;
|
365
|
+
case 3: // TEXT NODE
|
366
|
+
handler.chars(node.textContent);
|
367
|
+
break;
|
368
|
+
}
|
332
369
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
370
|
+
var nextNode;
|
371
|
+
if (!(nextNode = node.firstChild)) {
|
372
|
+
if (node.nodeType == 1) {
|
373
|
+
handler.end(node.nodeName.toLowerCase());
|
374
|
+
}
|
375
|
+
nextNode = node.nextSibling;
|
376
|
+
if (!nextNode) {
|
377
|
+
while (nextNode == null) {
|
378
|
+
node = node.parentNode;
|
379
|
+
if (node === inertBodyElement) break;
|
380
|
+
nextNode = node.nextSibling;
|
381
|
+
if (node.nodeType == 1) {
|
382
|
+
handler.end(node.nodeName.toLowerCase());
|
338
383
|
}
|
339
|
-
chars = false;
|
340
|
-
} else {
|
341
|
-
// no ending tag found --- this piece should be encoded as an entity.
|
342
|
-
text += '<';
|
343
|
-
html = html.substring(1);
|
344
384
|
}
|
345
385
|
}
|
346
|
-
|
347
|
-
if (chars) {
|
348
|
-
index = html.indexOf("<");
|
349
|
-
|
350
|
-
text += index < 0 ? html : html.substring(0, index);
|
351
|
-
html = index < 0 ? "" : html.substring(index);
|
352
|
-
|
353
|
-
if (handler.chars) handler.chars(decodeEntities(text));
|
354
|
-
}
|
355
|
-
|
356
|
-
} else {
|
357
|
-
// IE versions 9 and 10 do not understand the regex '[^]', so using a workaround with [\W\w].
|
358
|
-
html = html.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
|
359
|
-
function(all, text) {
|
360
|
-
text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1");
|
361
|
-
|
362
|
-
if (handler.chars) handler.chars(decodeEntities(text));
|
363
|
-
|
364
|
-
return "";
|
365
|
-
});
|
366
|
-
|
367
|
-
parseEndTag("", stack.last());
|
368
|
-
}
|
369
|
-
|
370
|
-
if (html == last) {
|
371
|
-
throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " +
|
372
|
-
"of html: {0}", html);
|
373
386
|
}
|
374
|
-
|
387
|
+
node = nextNode;
|
375
388
|
}
|
376
389
|
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
function parseStartTag(tag, tagName, rest, unary) {
|
381
|
-
tagName = angular.lowercase(tagName);
|
382
|
-
if (blockElements[tagName]) {
|
383
|
-
while (stack.last() && inlineElements[stack.last()]) {
|
384
|
-
parseEndTag("", stack.last());
|
385
|
-
}
|
386
|
-
}
|
387
|
-
|
388
|
-
if (optionalEndTagElements[tagName] && stack.last() == tagName) {
|
389
|
-
parseEndTag("", tagName);
|
390
|
-
}
|
391
|
-
|
392
|
-
unary = voidElements[tagName] || !!unary;
|
393
|
-
|
394
|
-
if (!unary) {
|
395
|
-
stack.push(tagName);
|
396
|
-
}
|
397
|
-
|
398
|
-
var attrs = {};
|
399
|
-
|
400
|
-
rest.replace(ATTR_REGEXP,
|
401
|
-
function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
|
402
|
-
var value = doubleQuotedValue
|
403
|
-
|| singleQuotedValue
|
404
|
-
|| unquotedValue
|
405
|
-
|| '';
|
406
|
-
|
407
|
-
attrs[name] = decodeEntities(value);
|
408
|
-
});
|
409
|
-
if (handler.start) handler.start(tagName, attrs, unary);
|
390
|
+
while (node = inertBodyElement.firstChild) {
|
391
|
+
inertBodyElement.removeChild(node);
|
410
392
|
}
|
393
|
+
}
|
411
394
|
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
for (pos = stack.length - 1; pos >= 0; pos--) {
|
418
|
-
if (stack[pos] == tagName) break;
|
419
|
-
}
|
420
|
-
}
|
421
|
-
|
422
|
-
if (pos >= 0) {
|
423
|
-
// Close all the open elements, up the stack
|
424
|
-
for (i = stack.length - 1; i >= pos; i--)
|
425
|
-
if (handler.end) handler.end(stack[i]);
|
426
|
-
|
427
|
-
// Remove the open elements from the stack
|
428
|
-
stack.length = pos;
|
429
|
-
}
|
395
|
+
function attrToMap(attrs) {
|
396
|
+
var map = {};
|
397
|
+
for (var i = 0, ii = attrs.length; i < ii; i++) {
|
398
|
+
var attr = attrs[i];
|
399
|
+
map[attr.name] = attr.value;
|
430
400
|
}
|
401
|
+
return map;
|
431
402
|
}
|
432
403
|
|
433
|
-
var hiddenPre=document.createElement("pre");
|
434
|
-
/**
|
435
|
-
* decodes all entities into regular string
|
436
|
-
* @param value
|
437
|
-
* @returns {string} A string with decoded entities.
|
438
|
-
*/
|
439
|
-
function decodeEntities(value) {
|
440
|
-
if (!value) { return ''; }
|
441
|
-
|
442
|
-
hiddenPre.innerHTML = value.replace(/</g,"<");
|
443
|
-
// innerText depends on styling as it doesn't display hidden elements.
|
444
|
-
// Therefore, it's better to use textContent not to cause unnecessary reflows.
|
445
|
-
return hiddenPre.textContent;
|
446
|
-
}
|
447
404
|
|
448
405
|
/**
|
449
406
|
* Escapes all potentially dangerous characters, so that the
|
@@ -469,24 +426,24 @@ function encodeEntities(value) {
|
|
469
426
|
|
470
427
|
/**
|
471
428
|
* create an HTML/XML writer which writes to buffer
|
472
|
-
* @param {Array} buf use buf.
|
429
|
+
* @param {Array} buf use buf.join('') to get out sanitized html string
|
473
430
|
* @returns {object} in the form of {
|
474
|
-
* start: function(tag, attrs
|
431
|
+
* start: function(tag, attrs) {},
|
475
432
|
* end: function(tag) {},
|
476
433
|
* chars: function(text) {},
|
477
434
|
* comment: function(text) {}
|
478
435
|
* }
|
479
436
|
*/
|
480
437
|
function htmlSanitizeWriter(buf, uriValidator) {
|
481
|
-
var
|
438
|
+
var ignoreCurrentElement = false;
|
482
439
|
var out = angular.bind(buf, buf.push);
|
483
440
|
return {
|
484
|
-
start: function(tag, attrs
|
441
|
+
start: function(tag, attrs) {
|
485
442
|
tag = angular.lowercase(tag);
|
486
|
-
if (!
|
487
|
-
|
443
|
+
if (!ignoreCurrentElement && blockedElements[tag]) {
|
444
|
+
ignoreCurrentElement = tag;
|
488
445
|
}
|
489
|
-
if (!
|
446
|
+
if (!ignoreCurrentElement && validElements[tag] === true) {
|
490
447
|
out('<');
|
491
448
|
out(tag);
|
492
449
|
angular.forEach(attrs, function(value, key) {
|
@@ -501,29 +458,63 @@ function htmlSanitizeWriter(buf, uriValidator) {
|
|
501
458
|
out('"');
|
502
459
|
}
|
503
460
|
});
|
504
|
-
out(
|
461
|
+
out('>');
|
505
462
|
}
|
506
463
|
},
|
507
464
|
end: function(tag) {
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
465
|
+
tag = angular.lowercase(tag);
|
466
|
+
if (!ignoreCurrentElement && validElements[tag] === true && voidElements[tag] !== true) {
|
467
|
+
out('</');
|
468
|
+
out(tag);
|
469
|
+
out('>');
|
470
|
+
}
|
471
|
+
if (tag == ignoreCurrentElement) {
|
472
|
+
ignoreCurrentElement = false;
|
473
|
+
}
|
474
|
+
},
|
518
475
|
chars: function(chars) {
|
519
|
-
|
520
|
-
|
521
|
-
}
|
476
|
+
if (!ignoreCurrentElement) {
|
477
|
+
out(encodeEntities(chars));
|
522
478
|
}
|
479
|
+
}
|
523
480
|
};
|
524
481
|
}
|
525
482
|
|
526
483
|
|
484
|
+
/**
|
485
|
+
* When IE9-11 comes across an unknown namespaced attribute e.g. 'xlink:foo' it adds 'xmlns:ns1' attribute to declare
|
486
|
+
* ns1 namespace and prefixes the attribute with 'ns1' (e.g. 'ns1:xlink:foo'). This is undesirable since we don't want
|
487
|
+
* to allow any of these custom attributes. This method strips them all.
|
488
|
+
*
|
489
|
+
* @param node Root element to process
|
490
|
+
*/
|
491
|
+
function stripCustomNsAttrs(node) {
|
492
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
493
|
+
var attrs = node.attributes;
|
494
|
+
for (var i = 0, l = attrs.length; i < l; i++) {
|
495
|
+
var attrNode = attrs[i];
|
496
|
+
var attrName = attrNode.name.toLowerCase();
|
497
|
+
if (attrName === 'xmlns:ns1' || attrName.indexOf('ns1:') === 0) {
|
498
|
+
node.removeAttributeNode(attrNode);
|
499
|
+
i--;
|
500
|
+
l--;
|
501
|
+
}
|
502
|
+
}
|
503
|
+
}
|
504
|
+
|
505
|
+
var nextNode = node.firstChild;
|
506
|
+
if (nextNode) {
|
507
|
+
stripCustomNsAttrs(nextNode);
|
508
|
+
}
|
509
|
+
|
510
|
+
nextNode = node.nextSibling;
|
511
|
+
if (nextNode) {
|
512
|
+
stripCustomNsAttrs(nextNode);
|
513
|
+
}
|
514
|
+
}
|
515
|
+
|
516
|
+
|
517
|
+
|
527
518
|
// define ngSanitize module and register $sanitize service
|
528
519
|
angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
|
529
520
|
|
@@ -535,14 +526,25 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
|
|
535
526
|
* @kind function
|
536
527
|
*
|
537
528
|
* @description
|
538
|
-
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
|
529
|
+
* Finds links in text input and turns them into html links. Supports `http/https/ftp/mailto` and
|
539
530
|
* plain email address links.
|
540
531
|
*
|
541
532
|
* Requires the {@link ngSanitize `ngSanitize`} module to be installed.
|
542
533
|
*
|
543
534
|
* @param {string} text Input text.
|
544
|
-
* @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
|
545
|
-
* @
|
535
|
+
* @param {string} target Window (`_blank|_self|_parent|_top`) or named frame to open links in.
|
536
|
+
* @param {object|function(url)} [attributes] Add custom attributes to the link element.
|
537
|
+
*
|
538
|
+
* Can be one of:
|
539
|
+
*
|
540
|
+
* - `object`: A map of attributes
|
541
|
+
* - `function`: Takes the url as a parameter and returns a map of attributes
|
542
|
+
*
|
543
|
+
* If the map of attributes contains a value for `target`, it overrides the value of
|
544
|
+
* the target parameter.
|
545
|
+
*
|
546
|
+
*
|
547
|
+
* @returns {string} Html-linkified and {@link $sanitize sanitized} text.
|
546
548
|
*
|
547
549
|
* @usage
|
548
550
|
<span ng-bind-html="linky_expression | linky"></span>
|
@@ -550,25 +552,13 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
|
|
550
552
|
* @example
|
551
553
|
<example module="linkyExample" deps="angular-sanitize.js">
|
552
554
|
<file name="index.html">
|
553
|
-
<script>
|
554
|
-
angular.module('linkyExample', ['ngSanitize'])
|
555
|
-
.controller('ExampleController', ['$scope', function($scope) {
|
556
|
-
$scope.snippet =
|
557
|
-
'Pretty text with some links:\n'+
|
558
|
-
'http://angularjs.org/,\n'+
|
559
|
-
'mailto:us@somewhere.org,\n'+
|
560
|
-
'another@somewhere.org,\n'+
|
561
|
-
'and one more: ftp://127.0.0.1/.';
|
562
|
-
$scope.snippetWithTarget = 'http://angularjs.org/';
|
563
|
-
}]);
|
564
|
-
</script>
|
565
555
|
<div ng-controller="ExampleController">
|
566
556
|
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
|
567
557
|
<table>
|
568
558
|
<tr>
|
569
|
-
<
|
570
|
-
<
|
571
|
-
<
|
559
|
+
<th>Filter</th>
|
560
|
+
<th>Source</th>
|
561
|
+
<th>Rendered</th>
|
572
562
|
</tr>
|
573
563
|
<tr id="linky-filter">
|
574
564
|
<td>linky filter</td>
|
@@ -582,10 +572,19 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
|
|
582
572
|
<tr id="linky-target">
|
583
573
|
<td>linky target</td>
|
584
574
|
<td>
|
585
|
-
<pre><div ng-bind-html="
|
575
|
+
<pre><div ng-bind-html="snippetWithSingleURL | linky:'_blank'"><br></div></pre>
|
576
|
+
</td>
|
577
|
+
<td>
|
578
|
+
<div ng-bind-html="snippetWithSingleURL | linky:'_blank'"></div>
|
579
|
+
</td>
|
580
|
+
</tr>
|
581
|
+
<tr id="linky-custom-attributes">
|
582
|
+
<td>linky custom attributes</td>
|
583
|
+
<td>
|
584
|
+
<pre><div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"><br></div></pre>
|
586
585
|
</td>
|
587
586
|
<td>
|
588
|
-
<div ng-bind-html="
|
587
|
+
<div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"></div>
|
589
588
|
</td>
|
590
589
|
</tr>
|
591
590
|
<tr id="escaped-html">
|
@@ -595,6 +594,18 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
|
|
595
594
|
</tr>
|
596
595
|
</table>
|
597
596
|
</file>
|
597
|
+
<file name="script.js">
|
598
|
+
angular.module('linkyExample', ['ngSanitize'])
|
599
|
+
.controller('ExampleController', ['$scope', function($scope) {
|
600
|
+
$scope.snippet =
|
601
|
+
'Pretty text with some links:\n'+
|
602
|
+
'http://angularjs.org/,\n'+
|
603
|
+
'mailto:us@somewhere.org,\n'+
|
604
|
+
'another@somewhere.org,\n'+
|
605
|
+
'and one more: ftp://127.0.0.1/.';
|
606
|
+
$scope.snippetWithSingleURL = 'http://angularjs.org/';
|
607
|
+
}]);
|
608
|
+
</file>
|
598
609
|
<file name="protractor.js" type="protractor">
|
599
610
|
it('should linkify the snippet with urls', function() {
|
600
611
|
expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
|
@@ -622,20 +633,32 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
|
|
622
633
|
|
623
634
|
it('should work with the target property', function() {
|
624
635
|
expect(element(by.id('linky-target')).
|
625
|
-
element(by.binding("
|
636
|
+
element(by.binding("snippetWithSingleURL | linky:'_blank'")).getText()).
|
626
637
|
toBe('http://angularjs.org/');
|
627
638
|
expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
|
628
639
|
});
|
640
|
+
|
641
|
+
it('should optionally add custom attributes', function() {
|
642
|
+
expect(element(by.id('linky-custom-attributes')).
|
643
|
+
element(by.binding("snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}")).getText()).
|
644
|
+
toBe('http://angularjs.org/');
|
645
|
+
expect(element(by.css('#linky-custom-attributes a')).getAttribute('rel')).toEqual('nofollow');
|
646
|
+
});
|
629
647
|
</file>
|
630
648
|
</example>
|
631
649
|
*/
|
632
650
|
angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
|
633
651
|
var LINKY_URL_REGEXP =
|
634
|
-
/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"
|
635
|
-
MAILTO_REGEXP = /^mailto
|
652
|
+
/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i,
|
653
|
+
MAILTO_REGEXP = /^mailto:/i;
|
654
|
+
|
655
|
+
var linkyMinErr = angular.$$minErr('linky');
|
656
|
+
var isString = angular.isString;
|
657
|
+
|
658
|
+
return function(text, target, attributes) {
|
659
|
+
if (text == null || text === '') return text;
|
660
|
+
if (!isString(text)) throw linkyMinErr('notstring', 'Expected string but received: {0}', text);
|
636
661
|
|
637
|
-
return function(text, target) {
|
638
|
-
if (!text) return text;
|
639
662
|
var match;
|
640
663
|
var raw = text;
|
641
664
|
var html = [];
|
@@ -664,8 +687,19 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
|
|
664
687
|
}
|
665
688
|
|
666
689
|
function addLink(url, text) {
|
690
|
+
var key;
|
667
691
|
html.push('<a ');
|
668
|
-
if (angular.
|
692
|
+
if (angular.isFunction(attributes)) {
|
693
|
+
attributes = attributes(url);
|
694
|
+
}
|
695
|
+
if (angular.isObject(attributes)) {
|
696
|
+
for (key in attributes) {
|
697
|
+
html.push(key + '="' + attributes[key] + '" ');
|
698
|
+
}
|
699
|
+
} else {
|
700
|
+
attributes = {};
|
701
|
+
}
|
702
|
+
if (angular.isDefined(target) && !('target' in attributes)) {
|
669
703
|
html.push('target="',
|
670
704
|
target,
|
671
705
|
'" ');
|