angularjs-rails 1.4.8 → 1.5.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/lib/angularjs-rails/version.rb +2 -2
- data/vendor/assets/javascripts/angular-animate.js +477 -286
- data/vendor/assets/javascripts/angular-aria.js +51 -51
- data/vendor/assets/javascripts/angular-cookies.js +9 -8
- data/vendor/assets/javascripts/angular-loader.js +45 -4
- data/vendor/assets/javascripts/angular-message-format.js +2 -2
- data/vendor/assets/javascripts/angular-messages.js +7 -5
- data/vendor/assets/javascripts/angular-mocks.js +406 -35
- data/vendor/assets/javascripts/angular-resource.js +121 -37
- data/vendor/assets/javascripts/angular-route.js +36 -11
- data/vendor/assets/javascripts/angular-sanitize.js +280 -246
- data/vendor/assets/javascripts/angular-scenario.js +2183 -773
- data/vendor/assets/javascripts/angular-touch.js +115 -14
- data/vendor/assets/javascripts/angular.js +2183 -773
- data/vendor/assets/javascripts/unstable/angular2-polyfills.js +3316 -0
- data/vendor/assets/javascripts/unstable/angular2.js +19327 -25066
- metadata +15 -14
@@ -1,6 +1,6 @@
|
|
1
1
|
/**
|
2
|
-
* @license AngularJS v1.
|
3
|
-
* (c) 2010-
|
2
|
+
* @license AngularJS v1.5.0
|
3
|
+
* (c) 2010-2016 Google, Inc. http://angularjs.org
|
4
4
|
* License: MIT
|
5
5
|
*/
|
6
6
|
(function(window, angular, undefined) {'use strict';
|
@@ -68,6 +68,9 @@ function shallowClearAndCopy(src, dst) {
|
|
68
68
|
* @ngdoc service
|
69
69
|
* @name $resource
|
70
70
|
* @requires $http
|
71
|
+
* @requires ng.$log
|
72
|
+
* @requires $q
|
73
|
+
* @requires ng.$timeout
|
71
74
|
*
|
72
75
|
* @description
|
73
76
|
* A factory which creates a resource object that lets you interact with
|
@@ -102,7 +105,7 @@ function shallowClearAndCopy(src, dst) {
|
|
102
105
|
* can escape it with `/\.`.
|
103
106
|
*
|
104
107
|
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
|
105
|
-
* `actions` methods. If
|
108
|
+
* `actions` methods. If a parameter value is a function, it will be executed every time
|
106
109
|
* when a param value needs to be obtained for a request (unless the param was overridden).
|
107
110
|
*
|
108
111
|
* Each key value in the parameter object is first bound to url template if present and then any
|
@@ -112,9 +115,9 @@ function shallowClearAndCopy(src, dst) {
|
|
112
115
|
* URL `/path/greet?salutation=Hello`.
|
113
116
|
*
|
114
117
|
* If the parameter value is prefixed with `@` then the value for that parameter will be extracted
|
115
|
-
* from the corresponding property on the `data` object (provided when calling an action method).
|
116
|
-
* example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of
|
117
|
-
* will be `data.someProp`.
|
118
|
+
* from the corresponding property on the `data` object (provided when calling an action method).
|
119
|
+
* For example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of
|
120
|
+
* `someParam` will be `data.someProp`.
|
118
121
|
*
|
119
122
|
* @param {Object.<Object>=} actions Hash with declaration of custom actions that should extend
|
120
123
|
* the default set of resource actions. The declaration should be created in the format of {@link
|
@@ -148,15 +151,21 @@ function shallowClearAndCopy(src, dst) {
|
|
148
151
|
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
|
149
152
|
* transform function or an array of such functions. The transform function takes the http
|
150
153
|
* response body and headers and returns its transformed (typically deserialized) version.
|
151
|
-
* By default, transformResponse will contain one function that checks if the response looks
|
152
|
-
* a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior,
|
153
|
-
* `transformResponse` to an empty array: `transformResponse: []`
|
154
|
+
* By default, transformResponse will contain one function that checks if the response looks
|
155
|
+
* like a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior,
|
156
|
+
* set `transformResponse` to an empty array: `transformResponse: []`
|
154
157
|
* - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
|
155
158
|
* GET request, otherwise if a cache instance built with
|
156
159
|
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
|
157
160
|
* caching.
|
158
|
-
* - **`timeout`** – `{number
|
159
|
-
*
|
161
|
+
* - **`timeout`** – `{number}` – timeout in milliseconds.<br />
|
162
|
+
* **Note:** In contrast to {@link ng.$http#usage $http.config}, {@link ng.$q promises} are
|
163
|
+
* **not** supported in $resource, because the same value would be used for multiple requests.
|
164
|
+
* If you are looking for a way to cancel requests, you should use the `cancellable` option.
|
165
|
+
* - **`cancellable`** – `{boolean}` – if set to true, the request made by a "non-instance" call
|
166
|
+
* will be cancelled (if not already completed) by calling `$cancelRequest()` on the call's
|
167
|
+
* return value. Calling `$cancelRequest()` for a non-cancellable or an already
|
168
|
+
* completed/cancelled request will have no effect.<br />
|
160
169
|
* - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the
|
161
170
|
* XHR object. See
|
162
171
|
* [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5)
|
@@ -168,12 +177,13 @@ function shallowClearAndCopy(src, dst) {
|
|
168
177
|
* with `http response` object. See {@link ng.$http $http interceptors}.
|
169
178
|
*
|
170
179
|
* @param {Object} options Hash with custom settings that should extend the
|
171
|
-
* default `$resourceProvider` behavior. The
|
172
|
-
*
|
173
|
-
* Where:
|
180
|
+
* default `$resourceProvider` behavior. The supported options are:
|
174
181
|
*
|
175
182
|
* - **`stripTrailingSlashes`** – {boolean} – If true then the trailing
|
176
183
|
* slashes from any calculated URL will be stripped. (Defaults to true.)
|
184
|
+
* - **`cancellable`** – {boolean} – If true, the request made by a "non-instance" call will be
|
185
|
+
* cancelled (if not already completed) by calling `$cancelRequest()` on the call's return value.
|
186
|
+
* This can be overwritten per action. (Defaults to false.)
|
177
187
|
*
|
178
188
|
* @returns {Object} A resource "class" object with methods for the default set of resource actions
|
179
189
|
* optionally extended with custom `actions`. The default set contains these actions:
|
@@ -221,7 +231,7 @@ function shallowClearAndCopy(src, dst) {
|
|
221
231
|
* Class actions return empty instance (with additional properties below).
|
222
232
|
* Instance actions return promise of the action.
|
223
233
|
*
|
224
|
-
* The Resource instances and
|
234
|
+
* The Resource instances and collections have these additional properties:
|
225
235
|
*
|
226
236
|
* - `$promise`: the {@link ng.$q promise} of the original server interaction that created this
|
227
237
|
* instance or collection.
|
@@ -231,7 +241,7 @@ function shallowClearAndCopy(src, dst) {
|
|
231
241
|
* {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view
|
232
242
|
* rendering until the resource(s) are loaded.
|
233
243
|
*
|
234
|
-
* On failure, the promise is
|
244
|
+
* On failure, the promise is rejected with the {@link ng.$http http response} object, without
|
235
245
|
* the `resource` property.
|
236
246
|
*
|
237
247
|
* If an interceptor object was provided, the promise will instead be resolved with the value
|
@@ -241,6 +251,11 @@ function shallowClearAndCopy(src, dst) {
|
|
241
251
|
* rejection), `false` before that. Knowing if the Resource has been resolved is useful in
|
242
252
|
* data-binding.
|
243
253
|
*
|
254
|
+
* The Resource instances and collections have these additional methods:
|
255
|
+
*
|
256
|
+
* - `$cancelRequest`: If there is a cancellable, pending request related to the instance or
|
257
|
+
* collection, calling this method will abort the request.
|
258
|
+
*
|
244
259
|
* @example
|
245
260
|
*
|
246
261
|
* # Credit card resource
|
@@ -285,6 +300,11 @@ function shallowClearAndCopy(src, dst) {
|
|
285
300
|
*
|
286
301
|
* Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and
|
287
302
|
* `headers`.
|
303
|
+
*
|
304
|
+
* @example
|
305
|
+
*
|
306
|
+
* # User resource
|
307
|
+
*
|
288
308
|
* When the data is returned from the server then the object is an instance of the resource type and
|
289
309
|
* all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
|
290
310
|
* operations (create, read, update, delete) on server-side data.
|
@@ -303,10 +323,10 @@ function shallowClearAndCopy(src, dst) {
|
|
303
323
|
*
|
304
324
|
```js
|
305
325
|
var User = $resource('/user/:userId', {userId:'@id'});
|
306
|
-
User.get({userId:123}, function(
|
307
|
-
|
308
|
-
|
309
|
-
//
|
326
|
+
User.get({userId:123}, function(user, getResponseHeaders){
|
327
|
+
user.abc = true;
|
328
|
+
user.$save(function(user, putResponseHeaders) {
|
329
|
+
//user => saved user object
|
310
330
|
//putResponseHeaders => $http header getter
|
311
331
|
});
|
312
332
|
});
|
@@ -321,8 +341,11 @@ function shallowClearAndCopy(src, dst) {
|
|
321
341
|
$scope.user = user;
|
322
342
|
});
|
323
343
|
```
|
324
|
-
|
344
|
+
*
|
345
|
+
* @example
|
346
|
+
*
|
325
347
|
* # Creating a custom 'PUT' request
|
348
|
+
*
|
326
349
|
* In this example we create a custom method on our resource to make a PUT request
|
327
350
|
* ```js
|
328
351
|
* var app = angular.module('app', ['ngResource', 'ngRoute']);
|
@@ -350,6 +373,34 @@ function shallowClearAndCopy(src, dst) {
|
|
350
373
|
* // This will PUT /notes/ID with the note object in the request payload
|
351
374
|
* }]);
|
352
375
|
* ```
|
376
|
+
*
|
377
|
+
* @example
|
378
|
+
*
|
379
|
+
* # Cancelling requests
|
380
|
+
*
|
381
|
+
* If an action's configuration specifies that it is cancellable, you can cancel the request related
|
382
|
+
* to an instance or collection (as long as it is a result of a "non-instance" call):
|
383
|
+
*
|
384
|
+
```js
|
385
|
+
// ...defining the `Hotel` resource...
|
386
|
+
var Hotel = $resource('/api/hotel/:id', {id: '@id'}, {
|
387
|
+
// Let's make the `query()` method cancellable
|
388
|
+
query: {method: 'get', isArray: true, cancellable: true}
|
389
|
+
});
|
390
|
+
|
391
|
+
// ...somewhere in the PlanVacationController...
|
392
|
+
...
|
393
|
+
this.onDestinationChanged = function onDestinationChanged(destination) {
|
394
|
+
// We don't care about any pending request for hotels
|
395
|
+
// in a different destination any more
|
396
|
+
this.availableHotels.$cancelRequest();
|
397
|
+
|
398
|
+
// Let's query for hotels in '<destination>'
|
399
|
+
// (calls: /api/hotel?location=<destination>)
|
400
|
+
this.availableHotels = Hotel.query({location: destination});
|
401
|
+
};
|
402
|
+
```
|
403
|
+
*
|
353
404
|
*/
|
354
405
|
angular.module('ngResource', ['ng']).
|
355
406
|
provider('$resource', function() {
|
@@ -370,7 +421,7 @@ angular.module('ngResource', ['ng']).
|
|
370
421
|
}
|
371
422
|
};
|
372
423
|
|
373
|
-
this.$get = ['$http', '$q', function($http, $q) {
|
424
|
+
this.$get = ['$http', '$log', '$q', '$timeout', function($http, $log, $q, $timeout) {
|
374
425
|
|
375
426
|
var noop = angular.noop,
|
376
427
|
forEach = angular.forEach,
|
@@ -438,7 +489,9 @@ angular.module('ngResource', ['ng']).
|
|
438
489
|
}
|
439
490
|
if (!(new RegExp("^\\d+$").test(param)) && param &&
|
440
491
|
(new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) {
|
441
|
-
urlParams[param] =
|
492
|
+
urlParams[param] = {
|
493
|
+
isQueryParamValue: (new RegExp("\\?.*=:" + param + "(?:\\W|$)")).test(url)
|
494
|
+
};
|
442
495
|
}
|
443
496
|
});
|
444
497
|
url = url.replace(/\\:/g, ':');
|
@@ -448,10 +501,14 @@ angular.module('ngResource', ['ng']).
|
|
448
501
|
});
|
449
502
|
|
450
503
|
params = params || {};
|
451
|
-
forEach(self.urlParams, function(
|
504
|
+
forEach(self.urlParams, function(paramInfo, urlParam) {
|
452
505
|
val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
|
453
506
|
if (angular.isDefined(val) && val !== null) {
|
454
|
-
|
507
|
+
if (paramInfo.isQueryParamValue) {
|
508
|
+
encodedVal = encodeUriQuery(val, true);
|
509
|
+
} else {
|
510
|
+
encodedVal = encodeUriSegment(val);
|
511
|
+
}
|
455
512
|
url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function(match, p1) {
|
456
513
|
return encodedVal + p1;
|
457
514
|
});
|
@@ -523,6 +580,20 @@ angular.module('ngResource', ['ng']).
|
|
523
580
|
|
524
581
|
forEach(actions, function(action, name) {
|
525
582
|
var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method);
|
583
|
+
var numericTimeout = action.timeout;
|
584
|
+
var cancellable = angular.isDefined(action.cancellable) ? action.cancellable :
|
585
|
+
(options && angular.isDefined(options.cancellable)) ? options.cancellable :
|
586
|
+
provider.defaults.cancellable;
|
587
|
+
|
588
|
+
if (numericTimeout && !angular.isNumber(numericTimeout)) {
|
589
|
+
$log.debug('ngResource:\n' +
|
590
|
+
' Only numeric values are allowed as `timeout`.\n' +
|
591
|
+
' Promises are not supported in $resource, because the same value would ' +
|
592
|
+
'be used for multiple requests. If you are looking for a way to cancel ' +
|
593
|
+
'requests, you should use the `cancellable` option.');
|
594
|
+
delete action.timeout;
|
595
|
+
numericTimeout = null;
|
596
|
+
}
|
526
597
|
|
527
598
|
Resource[name] = function(a1, a2, a3, a4) {
|
528
599
|
var params = {}, data, success, error;
|
@@ -571,6 +642,8 @@ angular.module('ngResource', ['ng']).
|
|
571
642
|
defaultResponseInterceptor;
|
572
643
|
var responseErrorInterceptor = action.interceptor && action.interceptor.responseError ||
|
573
644
|
undefined;
|
645
|
+
var timeoutDeferred;
|
646
|
+
var numericTimeoutPromise;
|
574
647
|
|
575
648
|
forEach(action, function(value, key) {
|
576
649
|
switch (key) {
|
@@ -580,21 +653,27 @@ angular.module('ngResource', ['ng']).
|
|
580
653
|
case 'params':
|
581
654
|
case 'isArray':
|
582
655
|
case 'interceptor':
|
583
|
-
|
584
|
-
case 'timeout':
|
585
|
-
httpConfig[key] = value;
|
656
|
+
case 'cancellable':
|
586
657
|
break;
|
587
658
|
}
|
588
659
|
});
|
589
660
|
|
661
|
+
if (!isInstanceCall && cancellable) {
|
662
|
+
timeoutDeferred = $q.defer();
|
663
|
+
httpConfig.timeout = timeoutDeferred.promise;
|
664
|
+
|
665
|
+
if (numericTimeout) {
|
666
|
+
numericTimeoutPromise = $timeout(timeoutDeferred.resolve, numericTimeout);
|
667
|
+
}
|
668
|
+
}
|
669
|
+
|
590
670
|
if (hasBody) httpConfig.data = data;
|
591
671
|
route.setUrlParams(httpConfig,
|
592
672
|
extend({}, extractParams(data, action.params || {}), params),
|
593
673
|
action.url);
|
594
674
|
|
595
675
|
var promise = $http(httpConfig).then(function(response) {
|
596
|
-
var data = response.data
|
597
|
-
promise = value.$promise;
|
676
|
+
var data = response.data;
|
598
677
|
|
599
678
|
if (data) {
|
600
679
|
// Need to convert action.isArray to boolean in case it is undefined
|
@@ -619,24 +698,28 @@ angular.module('ngResource', ['ng']).
|
|
619
698
|
}
|
620
699
|
});
|
621
700
|
} else {
|
701
|
+
var promise = value.$promise; // Save the promise
|
622
702
|
shallowClearAndCopy(data, value);
|
623
|
-
value.$promise = promise;
|
703
|
+
value.$promise = promise; // Restore the promise
|
624
704
|
}
|
625
705
|
}
|
626
|
-
|
627
|
-
value.$resolved = true;
|
628
|
-
|
629
706
|
response.resource = value;
|
630
707
|
|
631
708
|
return response;
|
632
709
|
}, function(response) {
|
633
|
-
value.$resolved = true;
|
634
|
-
|
635
710
|
(error || noop)(response);
|
636
|
-
|
637
711
|
return $q.reject(response);
|
638
712
|
});
|
639
713
|
|
714
|
+
promise.finally(function() {
|
715
|
+
value.$resolved = true;
|
716
|
+
if (!isInstanceCall && cancellable) {
|
717
|
+
value.$cancelRequest = angular.noop;
|
718
|
+
$timeout.cancel(numericTimeoutPromise);
|
719
|
+
timeoutDeferred = numericTimeoutPromise = httpConfig.timeout = null;
|
720
|
+
}
|
721
|
+
});
|
722
|
+
|
640
723
|
promise = promise.then(
|
641
724
|
function(response) {
|
642
725
|
var value = responseInterceptor(response);
|
@@ -651,6 +734,7 @@ angular.module('ngResource', ['ng']).
|
|
651
734
|
// - return the instance / collection
|
652
735
|
value.$promise = promise;
|
653
736
|
value.$resolved = false;
|
737
|
+
if (cancellable) value.$cancelRequest = timeoutDeferred.resolve;
|
654
738
|
|
655
739
|
return value;
|
656
740
|
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
/**
|
2
|
-
* @license AngularJS v1.
|
3
|
-
* (c) 2010-
|
2
|
+
* @license AngularJS v1.5.0
|
3
|
+
* (c) 2010-2016 Google, Inc. http://angularjs.org
|
4
4
|
* License: MIT
|
5
5
|
*/
|
6
6
|
(function(window, angular, undefined) {'use strict';
|
@@ -105,8 +105,17 @@ function $RouteProvider() {
|
|
105
105
|
* If all the promises are resolved successfully, the values of the resolved promises are
|
106
106
|
* injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is
|
107
107
|
* fired. If any of the promises are rejected the
|
108
|
-
* {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired.
|
109
|
-
*
|
108
|
+
* {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired.
|
109
|
+
* For easier access to the resolved dependencies from the template, the `resolve` map will
|
110
|
+
* be available on the scope of the route, under `$resolve` (by default) or a custom name
|
111
|
+
* specified by the `resolveAs` property (see below). This can be particularly useful, when
|
112
|
+
* working with {@link angular.Module#component components} as route templates.<br />
|
113
|
+
* <div class="alert alert-warning">
|
114
|
+
* **Note:** If your scope already contains a property with this name, it will be hidden
|
115
|
+
* or overwritten. Make sure, you specify an appropriate name for this property, that
|
116
|
+
* does not collide with other properties on the scope.
|
117
|
+
* </div>
|
118
|
+
* The map object is:
|
110
119
|
*
|
111
120
|
* - `key` – `{string}`: a name of a dependency to be injected into the controller.
|
112
121
|
* - `factory` - `{string|function}`: If `string` then it is an alias for a service.
|
@@ -116,7 +125,10 @@ function $RouteProvider() {
|
|
116
125
|
* `ngRoute.$routeParams` will still refer to the previous route within these resolve
|
117
126
|
* functions. Use `$route.current.params` to access the new route parameters, instead.
|
118
127
|
*
|
119
|
-
* - `
|
128
|
+
* - `resolveAs` - `{string=}` - The name under which the `resolve` map will be available on
|
129
|
+
* the scope of the route. If omitted, defaults to `$resolve`.
|
130
|
+
*
|
131
|
+
* - `redirectTo` – `{(string|function())=}` – value to update
|
120
132
|
* {@link ng.$location $location} path with and trigger route redirection.
|
121
133
|
*
|
122
134
|
* If `redirectTo` is a function, it will be called with the following parameters:
|
@@ -129,13 +141,13 @@ function $RouteProvider() {
|
|
129
141
|
* The custom `redirectTo` function is expected to return a string which will be used
|
130
142
|
* to update `$location.path()` and `$location.search()`.
|
131
143
|
*
|
132
|
-
* - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()`
|
144
|
+
* - `[reloadOnSearch=true]` - `{boolean=}` - reload route when only `$location.search()`
|
133
145
|
* or `$location.hash()` changes.
|
134
146
|
*
|
135
147
|
* If the option is set to `false` and url in the browser changes, then
|
136
148
|
* `$routeUpdate` event is broadcasted on the root scope.
|
137
149
|
*
|
138
|
-
* - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive
|
150
|
+
* - `[caseInsensitiveMatch=false]` - `{boolean=}` - match routes without being case sensitive
|
139
151
|
*
|
140
152
|
* If the option is set to `true`, then the particular route can be matched without being
|
141
153
|
* case sensitive
|
@@ -265,7 +277,7 @@ function $RouteProvider() {
|
|
265
277
|
* @property {Object} current Reference to the current route definition.
|
266
278
|
* The route definition contains:
|
267
279
|
*
|
268
|
-
* - `controller`: The controller constructor as
|
280
|
+
* - `controller`: The controller constructor as defined in the route definition.
|
269
281
|
* - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for
|
270
282
|
* controller instantiation. The `locals` contain
|
271
283
|
* the resolved values of the `resolve` map. Additionally the `locals` also contain:
|
@@ -273,6 +285,10 @@ function $RouteProvider() {
|
|
273
285
|
* - `$scope` - The current route scope.
|
274
286
|
* - `$template` - The current route template HTML.
|
275
287
|
*
|
288
|
+
* The `locals` will be assigned to the route scope's `$resolve` property. You can override
|
289
|
+
* the property name, using `resolveAs` in the route definition. See
|
290
|
+
* {@link ngRoute.$routeProvider $routeProvider} for more info.
|
291
|
+
*
|
276
292
|
* @property {Object} routes Object with all route configuration Objects as its properties.
|
277
293
|
*
|
278
294
|
* @description
|
@@ -468,10 +484,18 @@ function $RouteProvider() {
|
|
468
484
|
*/
|
469
485
|
reload: function() {
|
470
486
|
forceReload = true;
|
487
|
+
|
488
|
+
var fakeLocationEvent = {
|
489
|
+
defaultPrevented: false,
|
490
|
+
preventDefault: function fakePreventDefault() {
|
491
|
+
this.defaultPrevented = true;
|
492
|
+
forceReload = false;
|
493
|
+
}
|
494
|
+
};
|
495
|
+
|
471
496
|
$rootScope.$evalAsync(function() {
|
472
|
-
|
473
|
-
|
474
|
-
commitRoute();
|
497
|
+
prepareRoute(fakeLocationEvent);
|
498
|
+
if (!fakeLocationEvent.defaultPrevented) commitRoute();
|
475
499
|
});
|
476
500
|
},
|
477
501
|
|
@@ -981,6 +1005,7 @@ function ngViewFillContentFactory($compile, $controller, $route) {
|
|
981
1005
|
$element.data('$ngControllerController', controller);
|
982
1006
|
$element.children().data('$ngControllerController', controller);
|
983
1007
|
}
|
1008
|
+
scope[current.resolveAs || '$resolve'] = locals;
|
984
1009
|
|
985
1010
|
link(scope);
|
986
1011
|
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
/**
|
2
|
-
* @license AngularJS v1.
|
3
|
-
* (c) 2010-
|
2
|
+
* @license AngularJS v1.5.0
|
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,24 +245,23 @@ 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
267
|
'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' +
|
@@ -238,7 +269,7 @@ var htmlAttrs = makeMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspac
|
|
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,10 +633,17 @@ 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
|
*/
|
@@ -634,8 +652,13 @@ angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
|
|
634
652
|
/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i,
|
635
653
|
MAILTO_REGEXP = /^mailto:/i;
|
636
654
|
|
637
|
-
|
638
|
-
|
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);
|
661
|
+
|
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
|
'" ');
|