Update XStatic-Angular to 1.8.2

Related-Bug: #1927261

Change-Id: I8238e020df05825f6731499d027c0fd12cc2c00d
This commit is contained in:
manchandavishal 2021-05-08 00:17:46 +05:30
parent 8c1eb54d83
commit 030dabe172
18 changed files with 14840 additions and 31795 deletions

View File

@ -11,9 +11,9 @@ NAME = __name__.split('.')[-1] # package name (e.g. 'foo' or 'foo_bar')
# please use a all-lowercase valid python # please use a all-lowercase valid python
# package name # package name
VERSION = '1.5.8' # version of the packaged files, please use the upstream VERSION = '1.8.2' # version of the packaged files, please use the upstream
# version number # version number
BUILD = '0' # our package build number, so we can release new builds BUILD = '1' # our package build number, so we can release new builds
# with fixes for xstatic stuff. # with fixes for xstatic stuff.
PACKAGE_VERSION = VERSION + '.' + BUILD # version used for PyPi PACKAGE_VERSION = VERSION + '.' + BUILD # version used for PyPi

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/** /**
* @license AngularJS v1.5.8 * @license AngularJS v1.8.2
* (c) 2010-2016 Google, Inc. http://angularjs.org * (c) 2010-2020 Google, Inc. http://angularjs.org
* License: MIT * License: MIT
*/ */
(function(window, angular) {'use strict'; (function(window, angular) {'use strict';
@ -15,30 +15,28 @@
* attributes that convey state or semantic information about the application for users * attributes that convey state or semantic information about the application for users
* of assistive technologies, such as screen readers. * of assistive technologies, such as screen readers.
* *
* <div doc-module-components="ngAria"></div>
*
* ## Usage * ## Usage
* *
* For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following * For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following
* directives are supported: * directives are supported:
* `ngModel`, `ngChecked`, `ngReadonly`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`, * `ngModel`, `ngChecked`, `ngReadonly`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`,
* `ngDblClick`, and `ngMessages`. * `ngClick`, `ngDblClick`, and `ngMessages`.
* *
* Below is a more detailed breakdown of the attributes handled by ngAria: * Below is a more detailed breakdown of the attributes handled by ngAria:
* *
* | Directive | Supported Attributes | * | Directive | Supported Attributes |
* |---------------------------------------------|----------------------------------------------------------------------------------------| * |---------------------------------------------|-----------------------------------------------------------------------------------------------------|
* | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles | * | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles |
* | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled | * | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled |
* | {@link ng.directive:ngRequired ngRequired} | aria-required * | {@link ng.directive:ngRequired ngRequired} | aria-required |
* | {@link ng.directive:ngChecked ngChecked} | aria-checked * | {@link ng.directive:ngChecked ngChecked} | aria-checked |
* | {@link ng.directive:ngReadonly ngReadonly} | aria-readonly | * | {@link ng.directive:ngReadonly ngReadonly} | aria-readonly |
* | {@link ng.directive:ngValue ngValue} | aria-checked | * | {@link ng.directive:ngValue ngValue} | aria-checked |
* | {@link ng.directive:ngShow ngShow} | aria-hidden | * | {@link ng.directive:ngShow ngShow} | aria-hidden |
* | {@link ng.directive:ngHide ngHide} | aria-hidden | * | {@link ng.directive:ngHide ngHide} | aria-hidden |
* | {@link ng.directive:ngDblclick ngDblclick} | tabindex | * | {@link ng.directive:ngDblclick ngDblclick} | tabindex |
* | {@link module:ngMessages ngMessages} | aria-live | * | {@link module:ngMessages ngMessages} | aria-live |
* | {@link ng.directive:ngClick ngClick} | tabindex, keypress event, button role | * | {@link ng.directive:ngClick ngClick} | tabindex, keydown event, button role |
* *
* Find out more information about each directive by reading the * Find out more information about each directive by reading the
* {@link guide/accessibility ngAria Developer Guide}. * {@link guide/accessibility ngAria Developer Guide}.
@ -53,19 +51,25 @@
* <md-checkbox ng-disabled="disabled" aria-disabled="true"> * <md-checkbox ng-disabled="disabled" aria-disabled="true">
* ``` * ```
* *
* ## Disabling Attributes * ## Disabling Specific Attributes
* It's possible to disable individual attributes added by ngAria with the * It is possible to disable individual attributes added by ngAria with the
* {@link ngAria.$ariaProvider#config config} method. For more details, see the * {@link ngAria.$ariaProvider#config config} method. For more details, see the
* {@link guide/accessibility Developer Guide}. * {@link guide/accessibility Developer Guide}.
*
* ## Disabling `ngAria` on Specific Elements
* It is possible to make `ngAria` ignore a specific element, by adding the `ng-aria-disable`
* attribute on it. Note that only the element itself (and not its child elements) will be ignored.
*/ */
/* global -ngAriaModule */ var ARIA_DISABLE_ATTR = 'ngAriaDisable';
var ngAriaModule = angular.module('ngAria', ['ng']). var ngAriaModule = angular.module('ngAria', ['ng']).
info({ angularVersion: '"1.8.2"' }).
provider('$aria', $AriaProvider); provider('$aria', $AriaProvider);
/** /**
* Internal Utilities * Internal Utilities
*/ */
var nodeBlackList = ['BUTTON', 'A', 'INPUT', 'TEXTAREA', 'SELECT', 'DETAILS', 'SUMMARY']; var nativeAriaNodeNames = ['BUTTON', 'A', 'INPUT', 'TEXTAREA', 'SELECT', 'DETAILS', 'SUMMARY'];
var isNodeOneOf = function(elem, nodeTypeArray) { var isNodeOneOf = function(elem, nodeTypeArray) {
if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) { if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) {
@ -75,6 +79,7 @@ var isNodeOneOf = function(elem, nodeTypeArray) {
/** /**
* @ngdoc provider * @ngdoc provider
* @name $ariaProvider * @name $ariaProvider
* @this
* *
* @description * @description
* *
@ -103,7 +108,7 @@ function $AriaProvider() {
ariaInvalid: true, ariaInvalid: true,
ariaValue: true, ariaValue: true,
tabindex: true, tabindex: true,
bindKeypress: true, bindKeydown: true,
bindRoleForClick: true bindRoleForClick: true
}; };
@ -119,12 +124,15 @@ function $AriaProvider() {
* - **ariaDisabled** `{boolean}` Enables/disables aria-disabled tags * - **ariaDisabled** `{boolean}` Enables/disables aria-disabled tags
* - **ariaRequired** `{boolean}` Enables/disables aria-required tags * - **ariaRequired** `{boolean}` Enables/disables aria-required tags
* - **ariaInvalid** `{boolean}` Enables/disables aria-invalid tags * - **ariaInvalid** `{boolean}` Enables/disables aria-invalid tags
* - **ariaValue** `{boolean}` Enables/disables aria-valuemin, aria-valuemax and aria-valuenow tags * - **ariaValue** `{boolean}` Enables/disables aria-valuemin, aria-valuemax and
* aria-valuenow tags
* - **tabindex** `{boolean}` Enables/disables tabindex tags * - **tabindex** `{boolean}` Enables/disables tabindex tags
* - **bindKeypress** `{boolean}` Enables/disables keypress event binding on `div` and * - **bindKeydown** `{boolean}` Enables/disables keyboard event binding on non-interactive
* `li` elements with ng-click * elements (such as `div` or `li`) using ng-click, making them more accessible to users of
* - **bindRoleForClick** `{boolean}` Adds role=button to non-interactive elements like `div` * assistive technologies
* using ng-click, making them more accessible to users of assistive technologies * - **bindRoleForClick** `{boolean}` Adds role=button to non-interactive elements (such as
* `div` or `li`) using ng-click, making them more accessible to users of assistive
* technologies
* *
* @description * @description
* Enables/disables various ARIA attributes * Enables/disables various ARIA attributes
@ -133,10 +141,12 @@ function $AriaProvider() {
config = angular.extend(config, newConfig); config = angular.extend(config, newConfig);
}; };
function watchExpr(attrName, ariaAttr, nodeBlackList, negate) { function watchExpr(attrName, ariaAttr, nativeAriaNodeNames, negate) {
return function(scope, elem, attr) { return function(scope, elem, attr) {
if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return;
var ariaCamelName = attr.$normalize(ariaAttr); var ariaCamelName = attr.$normalize(ariaAttr);
if (config[ariaCamelName] && !isNodeOneOf(elem, nodeBlackList) && !attr[ariaCamelName]) { if (config[ariaCamelName] && !isNodeOneOf(elem, nativeAriaNodeNames) && !attr[ariaCamelName]) {
scope.$watch(attr[attrName], function(boolVal) { scope.$watch(attr[attrName], function(boolVal) {
// ensure boolean value // ensure boolean value
boolVal = negate ? !boolVal : !!boolVal; boolVal = negate ? !boolVal : !!boolVal;
@ -150,7 +160,6 @@ function $AriaProvider() {
* @name $aria * @name $aria
* *
* @description * @description
* @priority 200
* *
* The $aria service contains helper methods for applying common * The $aria service contains helper methods for applying common
* [ARIA](http://www.w3.org/TR/wai-aria/) attributes to HTML directives. * [ARIA](http://www.w3.org/TR/wai-aria/) attributes to HTML directives.
@ -161,7 +170,7 @@ function $AriaProvider() {
* *
*```js *```js
* ngAriaModule.directive('ngDisabled', ['$aria', function($aria) { * ngAriaModule.directive('ngDisabled', ['$aria', function($aria) {
* return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nodeBlackList, false); * return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nativeAriaNodeNames, false);
* }]) * }])
*``` *```
* Shown above, the ngAria module creates a directive with the same signature as the * Shown above, the ngAria module creates a directive with the same signature as the
@ -213,28 +222,31 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
return $aria.$$watchExpr('ngHide', 'aria-hidden', [], false); return $aria.$$watchExpr('ngHide', 'aria-hidden', [], false);
}]) }])
.directive('ngValue', ['$aria', function($aria) { .directive('ngValue', ['$aria', function($aria) {
return $aria.$$watchExpr('ngValue', 'aria-checked', nodeBlackList, false); return $aria.$$watchExpr('ngValue', 'aria-checked', nativeAriaNodeNames, false);
}]) }])
.directive('ngChecked', ['$aria', function($aria) { .directive('ngChecked', ['$aria', function($aria) {
return $aria.$$watchExpr('ngChecked', 'aria-checked', nodeBlackList, false); return $aria.$$watchExpr('ngChecked', 'aria-checked', nativeAriaNodeNames, false);
}]) }])
.directive('ngReadonly', ['$aria', function($aria) { .directive('ngReadonly', ['$aria', function($aria) {
return $aria.$$watchExpr('ngReadonly', 'aria-readonly', nodeBlackList, false); return $aria.$$watchExpr('ngReadonly', 'aria-readonly', nativeAriaNodeNames, false);
}]) }])
.directive('ngRequired', ['$aria', function($aria) { .directive('ngRequired', ['$aria', function($aria) {
return $aria.$$watchExpr('ngRequired', 'aria-required', nodeBlackList, false); return $aria.$$watchExpr('ngRequired', 'aria-required', nativeAriaNodeNames, false);
}]) }])
.directive('ngModel', ['$aria', function($aria) { .directive('ngModel', ['$aria', function($aria) {
function shouldAttachAttr(attr, normalizedAttr, elem, allowBlacklistEls) { function shouldAttachAttr(attr, normalizedAttr, elem, allowNonAriaNodes) {
return $aria.config(normalizedAttr) && !elem.attr(attr) && (allowBlacklistEls || !isNodeOneOf(elem, nodeBlackList)); return $aria.config(normalizedAttr) &&
!elem.attr(attr) &&
(allowNonAriaNodes || !isNodeOneOf(elem, nativeAriaNodeNames)) &&
(elem.attr('type') !== 'hidden' || elem[0].nodeName !== 'INPUT');
} }
function shouldAttachRole(role, elem) { function shouldAttachRole(role, elem) {
// if element does not have role attribute // if element does not have role attribute
// AND element type is equal to role (if custom element has a type equaling shape) <-- remove? // AND element type is equal to role (if custom element has a type equaling shape) <-- remove?
// AND element is not INPUT // AND element is not in nativeAriaNodeNames
return !elem.attr('role') && (elem.attr('type') === role) && (elem[0].nodeName !== 'INPUT'); return !elem.attr('role') && (elem.attr('type') === role) && !isNodeOneOf(elem, nativeAriaNodeNames);
} }
function getShape(attr, elem) { function getShape(attr, elem) {
@ -251,17 +263,11 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
require: 'ngModel', require: 'ngModel',
priority: 200, //Make sure watches are fired after any other directives that affect the ngModel value priority: 200, //Make sure watches are fired after any other directives that affect the ngModel value
compile: function(elem, attr) { compile: function(elem, attr) {
if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return;
var shape = getShape(attr, elem); var shape = getShape(attr, elem);
return { return {
pre: function(scope, elem, attr, ngModel) {
if (shape === 'checkbox') {
//Use the input[checkbox] $isEmpty implementation for elements with checkbox roles
ngModel.$isEmpty = function(value) {
return value === false;
};
}
},
post: function(scope, elem, attr, ngModel) { post: function(scope, elem, attr, ngModel) {
var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem, false); var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem, false);
@ -270,6 +276,8 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
} }
function getRadioReaction(newVal) { function getRadioReaction(newVal) {
// Strict comparison would cause a BC
// eslint-disable-next-line eqeqeq
var boolVal = (attr.value == ngModel.$viewValue); var boolVal = (attr.value == ngModel.$viewValue);
elem.attr('aria-checked', boolVal); elem.attr('aria-checked', boolVal);
} }
@ -346,13 +354,15 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
}; };
}]) }])
.directive('ngDisabled', ['$aria', function($aria) { .directive('ngDisabled', ['$aria', function($aria) {
return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nodeBlackList, false); return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nativeAriaNodeNames, false);
}]) }])
.directive('ngMessages', function() { .directive('ngMessages', function() {
return { return {
restrict: 'A', restrict: 'A',
require: '?ngMessages', require: '?ngMessages',
link: function(scope, elem, attr, ngMessages) { link: function(scope, elem, attr, ngMessages) {
if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return;
if (!elem.attr('aria-live')) { if (!elem.attr('aria-live')) {
elem.attr('aria-live', 'assertive'); elem.attr('aria-live', 'assertive');
} }
@ -363,10 +373,12 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
return { return {
restrict: 'A', restrict: 'A',
compile: function(elem, attr) { compile: function(elem, attr) {
var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true); if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return;
var fn = $parse(attr.ngClick);
return function(scope, elem, attr) { return function(scope, elem, attr) {
if (!isNodeOneOf(elem, nodeBlackList)) { if (!isNodeOneOf(elem, nativeAriaNodeNames)) {
if ($aria.config('bindRoleForClick') && !elem.attr('role')) { if ($aria.config('bindRoleForClick') && !elem.attr('role')) {
elem.attr('role', 'button'); elem.attr('role', 'button');
@ -376,10 +388,17 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
elem.attr('tabindex', 0); elem.attr('tabindex', 0);
} }
if ($aria.config('bindKeypress') && !attr.ngKeypress) { if ($aria.config('bindKeydown') && !attr.ngKeydown && !attr.ngKeypress && !attr.ngKeyup) {
elem.on('keypress', function(event) { elem.on('keydown', function(event) {
var keyCode = event.which || event.keyCode; var keyCode = event.which || event.keyCode;
if (keyCode === 32 || keyCode === 13) {
if (keyCode === 13 || keyCode === 32) {
// If the event is triggered on a non-interactive element ...
if (nativeAriaNodeNames.indexOf(event.target.nodeName) === -1 && !event.target.isContentEditable) {
// ... prevent the default browser behavior (e.g. scrolling when pressing spacebar)
// See https://github.com/angular/angular.js/issues/16664
event.preventDefault();
}
scope.$apply(callback); scope.$apply(callback);
} }
@ -395,7 +414,9 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
}]) }])
.directive('ngDblclick', ['$aria', function($aria) { .directive('ngDblclick', ['$aria', function($aria) {
return function(scope, elem, attr) { return function(scope, elem, attr) {
if ($aria.config('tabindex') && !elem.attr('tabindex') && !isNodeOneOf(elem, nodeBlackList)) { if (attr.hasOwnProperty(ARIA_DISABLE_ATTR)) return;
if ($aria.config('tabindex') && !elem.attr('tabindex') && !isNodeOneOf(elem, nativeAriaNodeNames)) {
elem.attr('tabindex', 0); elem.attr('tabindex', 0);
} }
}; };

View File

@ -1,6 +1,6 @@
/** /**
* @license AngularJS v1.5.8 * @license AngularJS v1.8.2
* (c) 2010-2016 Google, Inc. http://angularjs.org * (c) 2010-2020 Google, Inc. http://angularjs.org
* License: MIT * License: MIT
*/ */
(function(window, angular) {'use strict'; (function(window, angular) {'use strict';
@ -10,25 +10,21 @@
* @name ngCookies * @name ngCookies
* @description * @description
* *
* # ngCookies
*
* The `ngCookies` module provides a convenient wrapper for reading and writing browser cookies. * The `ngCookies` module provides a convenient wrapper for reading and writing browser cookies.
* *
*
* <div doc-module-components="ngCookies"></div>
*
* See {@link ngCookies.$cookies `$cookies`} for usage. * See {@link ngCookies.$cookies `$cookies`} for usage.
*/ */
angular.module('ngCookies', ['ng']). angular.module('ngCookies', ['ng']).
info({ angularVersion: '"1.8.2"' }).
/** /**
* @ngdoc provider * @ngdoc provider
* @name $cookiesProvider * @name $cookiesProvider
* @description * @description
* Use `$cookiesProvider` to change the default behavior of the {@link ngCookies.$cookies $cookies} service. * Use `$cookiesProvider` to change the default behavior of the {@link ngCookies.$cookies $cookies} service.
* */ * */
provider('$cookies', [function $CookiesProvider() { provider('$cookies', [/** @this */function $CookiesProvider() {
/** /**
* @ngdoc property * @ngdoc property
* @name $cookiesProvider#defaults * @name $cookiesProvider#defaults
@ -47,10 +43,24 @@ angular.module('ngCookies', ['ng']).
* or a Date object indicating the exact date/time this cookie will expire. * or a Date object indicating the exact date/time this cookie will expire.
* - **secure** - `{boolean}` - If `true`, then the cookie will only be available through a * - **secure** - `{boolean}` - If `true`, then the cookie will only be available through a
* secured connection. * secured connection.
* - **samesite** - `{string}` - prevents the browser from sending the cookie along with cross-site requests.
* Accepts the values `lax` and `strict`. See the [OWASP Wiki](https://www.owasp.org/index.php/SameSite)
* for more info. Note that as of May 2018, not all browsers support `SameSite`,
* so it cannot be used as a single measure against Cross-Site-Request-Forgery (CSRF) attacks.
* *
* Note: By default, the address that appears in your `<base>` tag will be used as the path. * Note: By default, the address that appears in your `<base>` tag will be used as the path.
* This is important so that cookies will be visible for all routes when html5mode is enabled. * This is important so that cookies will be visible for all routes when html5mode is enabled.
* *
* @example
*
* ```js
* angular.module('cookiesProviderExample', ['ngCookies'])
* .config(['$cookiesProvider', function($cookiesProvider) {
* // Setting default options
* $cookiesProvider.defaults.domain = 'foo.com';
* $cookiesProvider.defaults.secure = true;
* }]);
* ```
**/ **/
var defaults = this.defaults = {}; var defaults = this.defaults = {};
@ -66,7 +76,7 @@ angular.module('ngCookies', ['ng']).
* Provides read/write access to browser's cookies. * Provides read/write access to browser's cookies.
* *
* <div class="alert alert-info"> * <div class="alert alert-info">
* Up until Angular 1.3, `$cookies` exposed properties that represented the * Up until AngularJS 1.3, `$cookies` exposed properties that represented the
* current browser cookie values. In version 1.4, this behavior has changed, and * current browser cookie values. In version 1.4, this behavior has changed, and
* `$cookies` now provides a standard api of getters, setters etc. * `$cookies` now provides a standard api of getters, setters etc.
* </div> * </div>
@ -179,86 +189,6 @@ angular.module('ngCookies', ['ng']).
}]; }];
}]); }]);
angular.module('ngCookies').
/**
* @ngdoc service
* @name $cookieStore
* @deprecated
* @requires $cookies
*
* @description
* Provides a key-value (string-object) storage, that is backed by session cookies.
* Objects put or retrieved from this storage are automatically serialized or
* deserialized by angular's toJson/fromJson.
*
* Requires the {@link ngCookies `ngCookies`} module to be installed.
*
* <div class="alert alert-danger">
* **Note:** The $cookieStore service is **deprecated**.
* Please use the {@link ngCookies.$cookies `$cookies`} service instead.
* </div>
*
* @example
*
* ```js
* angular.module('cookieStoreExample', ['ngCookies'])
* .controller('ExampleController', ['$cookieStore', function($cookieStore) {
* // Put cookie
* $cookieStore.put('myFavorite','oatmeal');
* // Get cookie
* var favoriteCookie = $cookieStore.get('myFavorite');
* // Removing a cookie
* $cookieStore.remove('myFavorite');
* }]);
* ```
*/
factory('$cookieStore', ['$cookies', function($cookies) {
return {
/**
* @ngdoc method
* @name $cookieStore#get
*
* @description
* Returns the value of given cookie key
*
* @param {string} key Id to use for lookup.
* @returns {Object} Deserialized cookie value, undefined if the cookie does not exist.
*/
get: function(key) {
return $cookies.getObject(key);
},
/**
* @ngdoc method
* @name $cookieStore#put
*
* @description
* Sets a value for given cookie key
*
* @param {string} key Id for the `value`.
* @param {Object} value Value to be stored.
*/
put: function(key, value) {
$cookies.putObject(key, value);
},
/**
* @ngdoc method
* @name $cookieStore#remove
*
* @description
* Remove given cookie
*
* @param {string} key Id of the key-value pair to delete.
*/
remove: function(key) {
$cookies.remove(key);
}
};
}]);
/** /**
* @name $$cookieWriter * @name $$cookieWriter
* @requires $document * @requires $document
@ -292,6 +222,7 @@ function $$CookieWriter($document, $log, $browser) {
str += options.domain ? ';domain=' + options.domain : ''; str += options.domain ? ';domain=' + options.domain : '';
str += expires ? ';expires=' + expires.toUTCString() : ''; str += expires ? ';expires=' + expires.toUTCString() : '';
str += options.secure ? ';secure' : ''; str += options.secure ? ';secure' : '';
str += options.samesite ? ';samesite=' + options.samesite : '';
// per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum:
// - 300 cookies // - 300 cookies
@ -299,9 +230,9 @@ function $$CookieWriter($document, $log, $browser) {
// - 4096 bytes per cookie // - 4096 bytes per cookie
var cookieLength = str.length + 1; var cookieLength = str.length + 1;
if (cookieLength > 4096) { if (cookieLength > 4096) {
$log.warn("Cookie '" + name + $log.warn('Cookie \'' + name +
"' possibly not set or overflowed because it was too large (" + '\' possibly not set or overflowed because it was too large (' +
cookieLength + " > 4096 bytes)!"); cookieLength + ' > 4096 bytes)!');
} }
return str; return str;
@ -314,7 +245,7 @@ function $$CookieWriter($document, $log, $browser) {
$$CookieWriter.$inject = ['$document', '$log', '$browser']; $$CookieWriter.$inject = ['$document', '$log', '$browser'];
angular.module('ngCookies').provider('$$cookieWriter', function $$CookieWriterProvider() { angular.module('ngCookies').provider('$$cookieWriter', /** @this */ function $$CookieWriterProvider() {
this.$get = $$CookieWriter; this.$get = $$CookieWriter;
}); });

View File

@ -1,6 +1,6 @@
/** /**
* @license AngularJS v1.5.8 * @license AngularJS v1.8.2
* (c) 2010-2016 Google, Inc. http://angularjs.org * (c) 2010-2020 Google, Inc. http://angularjs.org
* License: MIT * License: MIT
*/ */
@ -9,9 +9,17 @@
/* global toDebugString: true */ /* global toDebugString: true */
function serializeObject(obj) { function serializeObject(obj, maxDepth) {
var seen = []; var seen = [];
// There is no direct way to stringify object until reaching a specific depth
// and a very deep object can cause a performance issue, so we copy the object
// based on this specific depth and then stringify it.
if (isValidObjectMaxDepth(maxDepth)) {
// This file is also included in `angular-loader`, so `copy()` might not always be available in
// the closure. Therefore, it is lazily retrieved as `angular.copy()` when needed.
obj = angular.copy(obj, null, maxDepth);
}
return JSON.stringify(obj, function(key, val) { return JSON.stringify(obj, function(key, val) {
val = toJsonReplacer(key, val); val = toJsonReplacer(key, val);
if (isObject(val)) { if (isObject(val)) {
@ -24,13 +32,13 @@ function serializeObject(obj) {
}); });
} }
function toDebugString(obj) { function toDebugString(obj, maxDepth) {
if (typeof obj === 'function') { if (typeof obj === 'function') {
return obj.toString().replace(/ \{[\s\S]*$/, ''); return obj.toString().replace(/ \{[\s\S]*$/, '');
} else if (isUndefined(obj)) { } else if (isUndefined(obj)) {
return 'undefined'; return 'undefined';
} else if (typeof obj !== 'string') { } else if (typeof obj !== 'string') {
return serializeObject(obj); return serializeObject(obj, maxDepth);
} }
return obj; return obj;
} }
@ -39,7 +47,7 @@ function toDebugString(obj) {
* @description * @description
* *
* This object provides a utility for producing rich Error messages within * This object provides a utility for producing rich Error messages within
* Angular. It can be called as follows: * AngularJS. It can be called as follows:
* *
* var exampleMinErr = minErr('example'); * var exampleMinErr = minErr('example');
* throw exampleMinErr('one', 'This {0} is {1}', foo, bar); * throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
@ -56,7 +64,7 @@ function toDebugString(obj) {
* Since data will be parsed statically during a build step, some restrictions * Since data will be parsed statically during a build step, some restrictions
* are applied with respect to how minErr instances are created and called. * are applied with respect to how minErr instances are created and called.
* Instances should have names of the form namespaceMinErr for a minErr created * Instances should have names of the form namespaceMinErr for a minErr created
* using minErr('namespace') . Error codes, namespaces and template strings * using minErr('namespace'). Error codes, namespaces and template strings
* should all be static strings, not variables or general expressions. * should all be static strings, not variables or general expressions.
* *
* @param {string} module The namespace to use for the new minErr instance. * @param {string} module The namespace to use for the new minErr instance.
@ -67,32 +75,41 @@ function toDebugString(obj) {
function minErr(module, ErrorConstructor) { function minErr(module, ErrorConstructor) {
ErrorConstructor = ErrorConstructor || Error; ErrorConstructor = ErrorConstructor || Error;
return function() {
var SKIP_INDEXES = 2;
var templateArgs = arguments, var url = 'https://errors.angularjs.org/"1.8.2"/';
code = templateArgs[0], var regex = url.replace('.', '\\.') + '[\\s\\S]*';
var errRegExp = new RegExp(regex, 'g');
return function() {
var code = arguments[0],
template = arguments[1],
message = '[' + (module ? module + ':' : '') + code + '] ', message = '[' + (module ? module + ':' : '') + code + '] ',
template = templateArgs[1], templateArgs = sliceArgs(arguments, 2).map(function(arg) {
return toDebugString(arg, minErrConfig.objectMaxDepth);
}),
paramPrefix, i; paramPrefix, i;
message += template.replace(/\{\d+\}/g, function(match) { // A minErr message has two parts: the message itself and the url that contains the
var index = +match.slice(1, -1), // encoded message.
shiftedIndex = index + SKIP_INDEXES; // The message's parameters can contain other error messages which also include error urls.
// To prevent the messages from getting too long, we strip the error urls from the parameters.
if (shiftedIndex < templateArgs.length) { message += template.replace(/\{\d+\}/g, function(match) {
return toDebugString(templateArgs[shiftedIndex]); var index = +match.slice(1, -1);
if (index < templateArgs.length) {
return templateArgs[index].replace(errRegExp, '');
} }
return match; return match;
}); });
message += '\nhttp://errors.angularjs.org/1.5.8/' + message += '\n' + url + (module ? module + '/' : '') + code;
(module ? module + '/' : '') + code;
for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') { if (minErrConfig.urlErrorParamsEnabled) {
message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' + for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
encodeURIComponent(toDebugString(templateArgs[i])); message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]);
}
} }
return new ErrorConstructor(message); return new ErrorConstructor(message);
@ -105,7 +122,7 @@ function minErr(module, ErrorConstructor) {
* @module ng * @module ng
* @description * @description
* *
* Interface for configuring angular {@link angular.module modules}. * Interface for configuring AngularJS {@link angular.module modules}.
*/ */
function setupModuleLoader(window) { function setupModuleLoader(window) {
@ -132,9 +149,9 @@ function setupModuleLoader(window) {
* @module ng * @module ng
* @description * @description
* *
* The `angular.module` is a global place for creating, registering and retrieving Angular * The `angular.module` is a global place for creating, registering and retrieving AngularJS
* modules. * modules.
* All modules (angular core or 3rd party) that should be available to an application must be * All modules (AngularJS core or 3rd party) that should be available to an application must be
* registered using this mechanism. * registered using this mechanism.
* *
* Passing one argument retrieves an existing {@link angular.Module}, * Passing one argument retrieves an existing {@link angular.Module},
@ -178,6 +195,9 @@ function setupModuleLoader(window) {
* @returns {angular.Module} new module with the {@link angular.Module} api. * @returns {angular.Module} new module with the {@link angular.Module} api.
*/ */
return function module(name, requires, configFn) { return function module(name, requires, configFn) {
var info = {};
var assertNotHasOwnProperty = function(name, context) { var assertNotHasOwnProperty = function(name, context) {
if (name === 'hasOwnProperty') { if (name === 'hasOwnProperty') {
throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
@ -190,9 +210,9 @@ function setupModuleLoader(window) {
} }
return ensure(modules, name, function() { return ensure(modules, name, function() {
if (!requires) { if (!requires) {
throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + throw $injectorMinErr('nomod', 'Module \'{0}\' is not available! You either misspelled ' +
"the module name or forgot to load it. If registering a module ensure that you " + 'the module name or forgot to load it. If registering a module ensure that you ' +
"specify the dependencies as the second argument.", name); 'specify the dependencies as the second argument.', name);
} }
/** @type {!Array.<Array.<*>>} */ /** @type {!Array.<Array.<*>>} */
@ -213,6 +233,45 @@ function setupModuleLoader(window) {
_configBlocks: configBlocks, _configBlocks: configBlocks,
_runBlocks: runBlocks, _runBlocks: runBlocks,
/**
* @ngdoc method
* @name angular.Module#info
* @module ng
*
* @param {Object=} info Information about the module
* @returns {Object|Module} The current info object for this module if called as a getter,
* or `this` if called as a setter.
*
* @description
* Read and write custom information about this module.
* For example you could put the version of the module in here.
*
* ```js
* angular.module('myModule', []).info({ version: '1.0.0' });
* ```
*
* The version could then be read back out by accessing the module elsewhere:
*
* ```
* var version = angular.module('myModule').info().version;
* ```
*
* You can also retrieve this information during runtime via the
* {@link $injector#modules `$injector.modules`} property:
*
* ```js
* var version = $injector.modules['myModule'].info().version;
* ```
*/
info: function(value) {
if (isDefined(value)) {
if (!isObject(value)) throw ngMinErr('aobj', 'Argument \'{0}\' must be an object', 'value');
info = value;
return this;
}
return info;
},
/** /**
* @ngdoc property * @ngdoc property
* @name angular.Module#requires * @name angular.Module#requires
@ -302,7 +361,7 @@ function setupModuleLoader(window) {
* @description * @description
* See {@link auto.$provide#decorator $provide.decorator()}. * See {@link auto.$provide#decorator $provide.decorator()}.
*/ */
decorator: invokeLaterAndSetModuleName('$provide', 'decorator'), decorator: invokeLaterAndSetModuleName('$provide', 'decorator', configBlocks),
/** /**
* @ngdoc method * @ngdoc method
@ -342,13 +401,13 @@ function setupModuleLoader(window) {
* @ngdoc method * @ngdoc method
* @name angular.Module#filter * @name angular.Module#filter
* @module ng * @module ng
* @param {string} name Filter name - this must be a valid angular expression identifier * @param {string} name Filter name - this must be a valid AngularJS expression identifier
* @param {Function} filterFactory Factory function for creating new instance of filter. * @param {Function} filterFactory Factory function for creating new instance of filter.
* @description * @description
* See {@link ng.$filterProvider#register $filterProvider.register()}. * See {@link ng.$filterProvider#register $filterProvider.register()}.
* *
* <div class="alert alert-warning"> * <div class="alert alert-warning">
* **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. * **Note:** Filter names must be valid AngularJS {@link expression} identifiers, such as `uppercase` or `orderBy`.
* Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
* your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
* (`myapp_subsection_filterx`). * (`myapp_subsection_filterx`).
@ -385,7 +444,8 @@ function setupModuleLoader(window) {
* @ngdoc method * @ngdoc method
* @name angular.Module#component * @name angular.Module#component
* @module ng * @module ng
* @param {string} name Name of the component in camel-case (i.e. myComp which will match as my-comp) * @param {string|Object} name Name of the component in camelCase (i.e. `myComp` which will match `<my-comp>`),
* or an object map of components where the keys are the names and the values are the component definition objects.
* @param {Object} options Component definition object (a simplified * @param {Object} options Component definition object (a simplified
* {@link ng.$compile#directive-definition-object directive definition object}) * {@link ng.$compile#directive-definition-object directive definition object})
* *
@ -401,7 +461,13 @@ function setupModuleLoader(window) {
* @param {Function} configFn Execute this function on module load. Useful for service * @param {Function} configFn Execute this function on module load. Useful for service
* configuration. * configuration.
* @description * @description
* Use this method to register work which needs to be performed on module loading. * Use this method to configure services by injecting their
* {@link angular.Module#provider `providers`}, e.g. for adding routes to the
* {@link ngRoute.$routeProvider $routeProvider}.
*
* Note that you can only inject {@link angular.Module#provider `providers`} and
* {@link angular.Module#constant `constants`} into this function.
*
* For more about how to configure services, see * For more about how to configure services, see
* {@link providers#provider-recipe Provider Recipe}. * {@link providers#provider-recipe Provider Recipe}.
*/ */
@ -448,10 +514,11 @@ function setupModuleLoader(window) {
* @param {string} method * @param {string} method
* @returns {angular.Module} * @returns {angular.Module}
*/ */
function invokeLaterAndSetModuleName(provider, method) { function invokeLaterAndSetModuleName(provider, method, queue) {
if (!queue) queue = invokeQueue;
return function(recipeName, factoryFunction) { return function(recipeName, factoryFunction) {
if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
invokeQueue.push([provider, method, arguments]); queue.push([provider, method, arguments]);
return moduleInstance; return moduleInstance;
}; };
} }
@ -481,4 +548,3 @@ setupModuleLoader(window);
* } } * } }
*/ */
angular.Module; angular.Module;

View File

@ -1,6 +1,6 @@
/** /**
* @license AngularJS v1.5.8 * @license AngularJS v1.8.2
* (c) 2010-2016 Google, Inc. http://angularjs.org * (c) 2010-2020 Google, Inc. http://angularjs.org
* License: MIT * License: MIT
*/ */
(function(window, angular) {'use strict'; (function(window, angular) {'use strict';
@ -13,22 +13,14 @@
/* global isFunction: false */ /* global isFunction: false */
/* global noop: false */ /* global noop: false */
/* global toJson: false */ /* global toJson: false */
/* global $$stringify: false */
function stringify(value) {
if (value == null /* null/undefined */) { return ''; }
switch (typeof value) {
case 'string': return value;
case 'number': return '' + value;
default: return toJson(value);
}
}
// Convert an index into the string into line/column for use in error messages // Convert an index into the string into line/column for use in error messages
// As such, this doesn't have to be efficient. // As such, this doesn't have to be efficient.
function indexToLineAndColumn(text, index) { function indexToLineAndColumn(text, index) {
var lines = text.split(/\n/g); var lines = text.split(/\n/g);
for (var i=0; i < lines.length; i++) { for (var i = 0; i < lines.length; i++) {
var line=lines[i]; var line = lines[i];
if (index >= line.length) { if (index >= line.length) {
index -= line.length; index -= line.length;
} else { } else {
@ -47,7 +39,7 @@ function parseTextLiteral(text) {
parsedFn['$$watchDelegate'] = function watchDelegate(scope, listener, objectEquality) { parsedFn['$$watchDelegate'] = function watchDelegate(scope, listener, objectEquality) {
var unwatch = scope['$watch'](noop, var unwatch = scope['$watch'](noop,
function textLiteralWatcher() { function textLiteralWatcher() {
if (isFunction(listener)) { listener.call(null, text, text, scope); } listener(text, text, scope);
unwatch(); unwatch();
}, },
objectEquality); objectEquality);
@ -64,14 +56,14 @@ function subtractOffset(expressionFn, offset) {
return expressionFn; return expressionFn;
} }
function minusOffset(value) { function minusOffset(value) {
return (value == void 0) ? value : value - offset; return (value == null) ? value : value - offset;
} }
function parsedFn(context) { return minusOffset(expressionFn(context)); } function parsedFn(context) { return minusOffset(expressionFn(context)); }
var unwatch; var unwatch;
parsedFn['$$watchDelegate'] = function watchDelegate(scope, listener, objectEquality) { parsedFn['$$watchDelegate'] = function watchDelegate(scope, listener, objectEquality) {
unwatch = scope['$watch'](expressionFn, unwatch = scope['$watch'](expressionFn,
function pluralExpressionWatchListener(newValue, oldValue) { function pluralExpressionWatchListener(newValue, oldValue) {
if (isFunction(listener)) { listener.call(null, minusOffset(newValue), minusOffset(oldValue), scope); } listener(minusOffset(newValue), minusOffset(oldValue), scope);
}, },
objectEquality); objectEquality);
return unwatch; return unwatch;
@ -96,7 +88,7 @@ function MessageSelectorBase(expressionFn, choices) {
var self = this; var self = this;
this.expressionFn = expressionFn; this.expressionFn = expressionFn;
this.choices = choices; this.choices = choices;
if (choices["other"] === void 0) { if (choices['other'] === undefined) {
throw $interpolateMinErr('reqother', '“other” is a required option.'); throw $interpolateMinErr('reqother', '“other” is a required option.');
} }
this.parsedFn = function(context) { return self.getResult(context); }; this.parsedFn = function(context) { return self.getResult(context); };
@ -130,7 +122,7 @@ function MessageSelectorWatchers(msgSelector, scope, listener, objectEquality) {
this.msgSelector = msgSelector; this.msgSelector = msgSelector;
this.listener = listener; this.listener = listener;
this.objectEquality = objectEquality; this.objectEquality = objectEquality;
this.lastMessage = void 0; this.lastMessage = undefined;
this.messageFnWatcher = noop; this.messageFnWatcher = noop;
var expressionFnListener = function(newValue, oldValue) { return self.expressionFnListener(newValue, oldValue); }; var expressionFnListener = function(newValue, oldValue) { return self.expressionFnListener(newValue, oldValue); };
this.expressionFnWatcher = scope['$watch'](msgSelector.expressionFn, expressionFnListener, objectEquality); this.expressionFnWatcher = scope['$watch'](msgSelector.expressionFn, expressionFnListener, objectEquality);
@ -145,9 +137,7 @@ MessageSelectorWatchers.prototype.expressionFnListener = function expressionFnLi
}; };
MessageSelectorWatchers.prototype.messageFnListener = function messageFnListener(newMessage, oldMessage) { MessageSelectorWatchers.prototype.messageFnListener = function messageFnListener(newMessage, oldMessage) {
if (isFunction(this.listener)) { this.listener.call(null, newMessage, newMessage === oldMessage ? newMessage : this.lastMessage, this.scope);
this.listener.call(null, newMessage, newMessage === oldMessage ? newMessage : this.lastMessage, this.scope);
}
this.lastMessage = newMessage; this.lastMessage = newMessage;
}; };
@ -170,7 +160,7 @@ SelectMessageProto.prototype = MessageSelectorBase.prototype;
SelectMessage.prototype = new SelectMessageProto(); SelectMessage.prototype = new SelectMessageProto();
SelectMessage.prototype.categorizeValue = function categorizeSelectValue(value) { SelectMessage.prototype.categorizeValue = function categorizeSelectValue(value) {
return (this.choices[value] !== void 0) ? value : "other"; return (this.choices[value] !== undefined) ? value : 'other';
}; };
/** /**
@ -190,12 +180,12 @@ PluralMessageProto.prototype = MessageSelectorBase.prototype;
PluralMessage.prototype = new PluralMessageProto(); PluralMessage.prototype = new PluralMessageProto();
PluralMessage.prototype.categorizeValue = function categorizePluralValue(value) { PluralMessage.prototype.categorizeValue = function categorizePluralValue(value) {
if (isNaN(value)) { if (isNaN(value)) {
return "other"; return 'other';
} else if (this.choices[value] !== void 0) { } else if (this.choices[value] !== undefined) {
return value; return value;
} else { } else {
var category = this.pluralCat(value - this.offset); var category = this.pluralCat(value - this.offset);
return (this.choices[category] !== void 0) ? category : "other"; return (this.choices[category] !== undefined) ? category : 'other';
} }
}; };
@ -264,7 +254,7 @@ InterpolationParts.prototype.getExpressionValues = function getExpressionValues(
InterpolationParts.prototype.getResult = function getResult(expressionValues) { InterpolationParts.prototype.getResult = function getResult(expressionValues) {
for (var i = 0; i < this.expressionIndices.length; i++) { for (var i = 0; i < this.expressionIndices.length; i++) {
var expressionValue = expressionValues[i]; var expressionValue = expressionValues[i];
if (this.allOrNothing && expressionValue === void 0) return; if (this.allOrNothing && expressionValue === undefined) return;
this.textParts[this.expressionIndices[i]] = expressionValue; this.textParts[this.expressionIndices[i]] = expressionValue;
} }
return this.textParts.join(''); return this.textParts.join('');
@ -275,7 +265,7 @@ InterpolationParts.prototype.toParsedFn = function toParsedFn(mustHaveExpression
var self = this; var self = this;
this.flushPartialText(); this.flushPartialText();
if (mustHaveExpression && this.expressionFns.length === 0) { if (mustHaveExpression && this.expressionFns.length === 0) {
return void 0; return undefined;
} }
if (this.textParts.length === 0) { if (this.textParts.length === 0) {
return parseTextLiteral(''); return parseTextLiteral('');
@ -284,7 +274,7 @@ InterpolationParts.prototype.toParsedFn = function toParsedFn(mustHaveExpression
$interpolateMinErr['throwNoconcat'](originalText); $interpolateMinErr['throwNoconcat'](originalText);
} }
if (this.expressionFns.length === 0) { if (this.expressionFns.length === 0) {
if (this.textParts.length != 1) { this.errorInParseLogic(); } if (this.textParts.length !== 1) { this.errorInParseLogic(); }
return parseTextLiteral(this.textParts[0]); return parseTextLiteral(this.textParts[0]);
} }
var parsedFn = function(context) { var parsedFn = function(context) {
@ -311,7 +301,7 @@ InterpolationParts.prototype.watchDelegate = function watchDelegate(scope, liste
function InterpolationPartsWatcher(interpolationParts, scope, listener, objectEquality) { function InterpolationPartsWatcher(interpolationParts, scope, listener, objectEquality) {
this.interpolationParts = interpolationParts; this.interpolationParts = interpolationParts;
this.scope = scope; this.scope = scope;
this.previousResult = (void 0); this.previousResult = (undefined);
this.listener = listener; this.listener = listener;
var self = this; var self = this;
this.expressionFnsWatcher = scope['$watchGroup'](interpolationParts.expressionFns, function(newExpressionValues, oldExpressionValues) { this.expressionFnsWatcher = scope['$watchGroup'](interpolationParts.expressionFns, function(newExpressionValues, oldExpressionValues) {
@ -321,9 +311,7 @@ function InterpolationPartsWatcher(interpolationParts, scope, listener, objectEq
InterpolationPartsWatcher.prototype.watchListener = function watchListener(newExpressionValues, oldExpressionValues) { InterpolationPartsWatcher.prototype.watchListener = function watchListener(newExpressionValues, oldExpressionValues) {
var result = this.interpolationParts.getResult(newExpressionValues); var result = this.interpolationParts.getResult(newExpressionValues);
if (isFunction(this.listener)) { this.listener.call(null, result, newExpressionValues === oldExpressionValues ? result : this.previousResult, this.scope);
this.listener.call(null, result, newExpressionValues === oldExpressionValues ? result : this.previousResult, this.scope);
}
this.previousResult = result; this.previousResult = result;
}; };
@ -423,7 +411,7 @@ MessageFormatParser.prototype.popState = function popState() {
MessageFormatParser.prototype.matchRe = function matchRe(re, search) { MessageFormatParser.prototype.matchRe = function matchRe(re, search) {
re.lastIndex = this.index; re.lastIndex = this.index;
var match = re.exec(this.text); var match = re.exec(this.text);
if (match != null && (search === true || (match.index == this.index))) { if (match != null && (search === true || (match.index === this.index))) {
this.index = re.lastIndex; this.index = re.lastIndex;
return match; return match;
} }
@ -461,7 +449,7 @@ MessageFormatParser.prototype.errorInParseLogic = function errorInParseLogic() {
}; };
MessageFormatParser.prototype.assertRuleOrNull = function assertRuleOrNull(rule) { MessageFormatParser.prototype.assertRuleOrNull = function assertRuleOrNull(rule) {
if (rule === void 0) { if (rule === undefined) {
this.errorInParseLogic(); this.errorInParseLogic();
} }
}; };
@ -477,7 +465,7 @@ MessageFormatParser.prototype.errorExpecting = function errorExpecting() {
position.line, position.column, this.text); position.line, position.column, this.text);
} }
var word = match[1]; var word = match[1];
if (word == "select" || word == "plural") { if (word === 'select' || word === 'plural') {
position = indexToLineAndColumn(this.text, this.index); position = indexToLineAndColumn(this.text, this.index);
throw $interpolateMinErr('reqcomma', throw $interpolateMinErr('reqcomma',
'Expected a comma after the keyword “{0}” at line {1}, column {2} of text “{3}”', 'Expected a comma after the keyword “{0}” at line {1}, column {2} of text “{3}”',
@ -505,7 +493,7 @@ MessageFormatParser.prototype.ruleString = function ruleString() {
MessageFormatParser.prototype.startStringAtMatch = function startStringAtMatch(match) { MessageFormatParser.prototype.startStringAtMatch = function startStringAtMatch(match) {
this.stringStartIndex = match.index; this.stringStartIndex = match.index;
this.stringQuote = match[0]; this.stringQuote = match[0];
this.stringInterestsRe = this.stringQuote == "'" ? SQUOTED_STRING_INTEREST_RE : DQUOTED_STRING_INTEREST_RE; this.stringInterestsRe = this.stringQuote === '\'' ? SQUOTED_STRING_INTEREST_RE : DQUOTED_STRING_INTEREST_RE;
this.rule = this.ruleInsideString; this.rule = this.ruleInsideString;
}; };
@ -519,8 +507,7 @@ MessageFormatParser.prototype.ruleInsideString = function ruleInsideString() {
'The string beginning at line {0}, column {1} is unterminated in text “{2}”', 'The string beginning at line {0}, column {1} is unterminated in text “{2}”',
position.line, position.column, this.text); position.line, position.column, this.text);
} }
var chars = match[0]; if (match[0] === this.stringQuote) {
if (match == this.stringQuote) {
this.rule = null; this.rule = null;
} }
}; };
@ -533,8 +520,8 @@ MessageFormatParser.prototype.rulePluralOrSelect = function rulePluralOrSelect()
} }
var argType = match[1]; var argType = match[1];
switch (argType) { switch (argType) {
case "plural": this.rule = this.rulePluralStyle; break; case 'plural': this.rule = this.rulePluralStyle; break;
case "select": this.rule = this.ruleSelectStyle; break; case 'select': this.rule = this.ruleSelectStyle; break;
default: this.errorInParseLogic(); default: this.errorInParseLogic();
} }
}; };
@ -552,7 +539,7 @@ MessageFormatParser.prototype.ruleSelectStyle = function ruleSelectStyle() {
}; };
var NUMBER_RE = /[0]|(?:[1-9][0-9]*)/g; var NUMBER_RE = /[0]|(?:[1-9][0-9]*)/g;
var PLURAL_OFFSET_RE = new RegExp("\\s*offset\\s*:\\s*(" + NUMBER_RE.source + ")", "g"); var PLURAL_OFFSET_RE = new RegExp('\\s*offset\\s*:\\s*(' + NUMBER_RE.source + ')', 'g');
MessageFormatParser.prototype.rulePluralOffset = function rulePluralOffset() { MessageFormatParser.prototype.rulePluralOffset = function rulePluralOffset() {
var match = this.matchRe(PLURAL_OFFSET_RE); var match = this.matchRe(PLURAL_OFFSET_RE);
@ -562,7 +549,7 @@ MessageFormatParser.prototype.rulePluralOffset = function rulePluralOffset() {
}; };
MessageFormatParser.prototype.assertChoiceKeyIsNew = function assertChoiceKeyIsNew(choiceKey, index) { MessageFormatParser.prototype.assertChoiceKeyIsNew = function assertChoiceKeyIsNew(choiceKey, index) {
if (this.choices[choiceKey] !== void 0) { if (this.choices[choiceKey] !== undefined) {
var position = indexToLineAndColumn(this.text, index); var position = indexToLineAndColumn(this.text, index);
throw $interpolateMinErr('dupvalue', throw $interpolateMinErr('dupvalue',
'The choice “{0}” is specified more than once. Duplicate key is at line {1}, column {2} in text “{3}”', 'The choice “{0}” is specified more than once. Duplicate key is at line {1}, column {2} in text “{3}”',
@ -583,7 +570,7 @@ MessageFormatParser.prototype.ruleSelectKeyword = function ruleSelectKeyword() {
this.rule = this.ruleMessageText; this.rule = this.ruleMessageText;
}; };
var EXPLICIT_VALUE_OR_KEYWORD_RE = new RegExp("\\s*(?:(?:=(" + NUMBER_RE.source + "))|(\\w+))", "g"); var EXPLICIT_VALUE_OR_KEYWORD_RE = new RegExp('\\s*(?:(?:=(' + NUMBER_RE.source + '))|(\\w+))', 'g');
MessageFormatParser.prototype.rulePluralValueOrKeyword = function rulePluralValueOrKeyword() { MessageFormatParser.prototype.rulePluralValueOrKeyword = function rulePluralValueOrKeyword() {
var match = this.matchRe(EXPLICIT_VALUE_OR_KEYWORD_RE); var match = this.matchRe(EXPLICIT_VALUE_OR_KEYWORD_RE);
if (match == null) { if (match == null) {
@ -600,7 +587,7 @@ MessageFormatParser.prototype.rulePluralValueOrKeyword = function rulePluralValu
this.rule = this.ruleMessageText; this.rule = this.ruleMessageText;
}; };
var BRACE_OPEN_RE = /\s*{/g; var BRACE_OPEN_RE = /\s*\{/g;
var BRACE_CLOSE_RE = /}/g; var BRACE_CLOSE_RE = /}/g;
MessageFormatParser.prototype.ruleMessageText = function ruleMessageText() { MessageFormatParser.prototype.ruleMessageText = function ruleMessageText() {
if (!this.consumeRe(BRACE_OPEN_RE)) { if (!this.consumeRe(BRACE_OPEN_RE)) {
@ -620,7 +607,7 @@ var INTERP_OR_END_MESSAGE_RE = /\\.|{{|}/g;
var INTERP_OR_PLURALVALUE_OR_END_MESSAGE_RE = /\\.|{{|#|}/g; var INTERP_OR_PLURALVALUE_OR_END_MESSAGE_RE = /\\.|{{|#|}/g;
var ESCAPE_OR_MUSTACHE_BEGIN_RE = /\\.|{{/g; var ESCAPE_OR_MUSTACHE_BEGIN_RE = /\\.|{{/g;
MessageFormatParser.prototype.advanceInInterpolationOrMessageText = function advanceInInterpolationOrMessageText() { MessageFormatParser.prototype.advanceInInterpolationOrMessageText = function advanceInInterpolationOrMessageText() {
var currentIndex = this.index, match, re; var currentIndex = this.index, match;
if (this.ruleChoiceKeyword == null) { // interpolation if (this.ruleChoiceKeyword == null) { // interpolation
match = this.searchRe(ESCAPE_OR_MUSTACHE_BEGIN_RE); match = this.searchRe(ESCAPE_OR_MUSTACHE_BEGIN_RE);
if (match == null) { // End of interpolation text. Nothing more to process. if (match == null) { // End of interpolation text. Nothing more to process.
@ -629,7 +616,7 @@ MessageFormatParser.prototype.advanceInInterpolationOrMessageText = function adv
return null; return null;
} }
} else { } else {
match = this.searchRe(this.ruleChoiceKeyword == this.rulePluralValueOrKeyword ? match = this.searchRe(this.ruleChoiceKeyword === this.rulePluralValueOrKeyword ?
INTERP_OR_PLURALVALUE_OR_END_MESSAGE_RE : INTERP_OR_END_MESSAGE_RE); INTERP_OR_PLURALVALUE_OR_END_MESSAGE_RE : INTERP_OR_END_MESSAGE_RE);
if (match == null) { if (match == null) {
var position = indexToLineAndColumn(this.text, this.msgStartIndex); var position = indexToLineAndColumn(this.text, this.msgStartIndex);
@ -654,20 +641,20 @@ MessageFormatParser.prototype.ruleInInterpolationOrMessageText = function ruleIn
this.rule = null; this.rule = null;
return; return;
} }
if (token[0] == "\\") { if (token[0] === '\\') {
// unescape next character and continue // unescape next character and continue
this.interpolationParts.addText(this.textPart + token[1]); this.interpolationParts.addText(this.textPart + token[1]);
return; return;
} }
this.interpolationParts.addText(this.textPart); this.interpolationParts.addText(this.textPart);
if (token == "{{") { if (token === '{{') {
this.pushState(); this.pushState();
this.ruleStack.push(this.ruleEndMustacheInInterpolationOrMessage); this.ruleStack.push(this.ruleEndMustacheInInterpolationOrMessage);
this.rule = this.ruleEnteredMustache; this.rule = this.ruleEnteredMustache;
} else if (token == "}") { } else if (token === '}') {
this.choices[this.choiceKey] = this.interpolationParts.toParsedFn(/*mustHaveExpression=*/false, this.text); this.choices[this.choiceKey] = this.interpolationParts.toParsedFn(/*mustHaveExpression=*/false, this.text);
this.rule = this.ruleChoiceKeyword; this.rule = this.ruleChoiceKeyword;
} else if (token == "#") { } else if (token === '#') {
this.interpolationParts.addExpressionFn(this.expressionMinusOffsetFn); this.interpolationParts.addExpressionFn(this.expressionMinusOffsetFn);
} else { } else {
this.errorInParseLogic(); this.errorInParseLogic();
@ -691,7 +678,7 @@ MessageFormatParser.prototype.ruleInInterpolation = function ruleInInterpolation
return; return;
} }
var token = match[0]; var token = match[0];
if (token[0] == "\\") { if (token[0] === '\\') {
// unescape next character and continue // unescape next character and continue
this.interpolationParts.addText(this.text.substring(currentIndex, match.index) + token[1]); this.interpolationParts.addText(this.text.substring(currentIndex, match.index) + token[1]);
return; return;
@ -738,7 +725,7 @@ MessageFormatParser.prototype.ruleEndMustache = function ruleEndMustache() {
// day), then the result *has* to be a string and those rules would have already set // day), then the result *has* to be a string and those rules would have already set
// this.parsedFn. If there was no MessageFormat extension, then there is no requirement to // this.parsedFn. If there was no MessageFormat extension, then there is no requirement to
// stringify the result and parsedFn isn't set. We set it here. While we could have set it // stringify the result and parsedFn isn't set. We set it here. While we could have set it
// unconditionally when exiting the Angular expression, I intend for us to not just replace // unconditionally when exiting the AngularJS expression, I intend for us to not just replace
// $interpolate, but also to replace $parse in a future version (so ng-bind can work), and in // $interpolate, but also to replace $parse in a future version (so ng-bind can work), and in
// such a case we do not want to unnecessarily stringify something if it's not going to be used // such a case we do not want to unnecessarily stringify something if it's not going to be used
// in a string context. // in a string context.
@ -757,18 +744,18 @@ MessageFormatParser.prototype.ruleAngularExpression = function ruleAngularExpres
function getEndOperator(opBegin) { function getEndOperator(opBegin) {
switch (opBegin) { switch (opBegin) {
case "{": return "}"; case '{': return '}';
case "[": return "]"; case '[': return ']';
case "(": return ")"; case '(': return ')';
default: return null; default: return null;
} }
} }
function getBeginOperator(opEnd) { function getBeginOperator(opEnd) {
switch (opEnd) { switch (opEnd) {
case "}": return "{"; case '}': return '{';
case "]": return "["; case ']': return '[';
case ")": return "("; case ')': return '(';
default: return null; default: return null;
} }
} }
@ -778,12 +765,11 @@ function getBeginOperator(opEnd) {
// should support any other type of start/end interpolation symbol. // should support any other type of start/end interpolation symbol.
var INTERESTING_OPERATORS_RE = /[[\]{}()'",]/g; var INTERESTING_OPERATORS_RE = /[[\]{}()'",]/g;
MessageFormatParser.prototype.ruleInAngularExpression = function ruleInAngularExpression() { MessageFormatParser.prototype.ruleInAngularExpression = function ruleInAngularExpression() {
var startIndex = this.index;
var match = this.searchRe(INTERESTING_OPERATORS_RE); var match = this.searchRe(INTERESTING_OPERATORS_RE);
var position; var position;
if (match == null) { if (match == null) {
if (this.angularOperatorStack.length === 0) { if (this.angularOperatorStack.length === 0) {
// This is the end of the Angular expression so this is actually a // This is the end of the AngularJS expression so this is actually a
// success. Note that when inside an interpolation, this means we even // success. Note that when inside an interpolation, this means we even
// consumed the closing interpolation symbols if they were curlies. This // consumed the closing interpolation symbols if they were curlies. This
// is NOT an error at this point but will become an error further up the // is NOT an error at this point but will become an error further up the
@ -799,16 +785,16 @@ MessageFormatParser.prototype.ruleInAngularExpression = function ruleInAngularEx
} }
var innermostOperator = this.angularOperatorStack[0]; var innermostOperator = this.angularOperatorStack[0];
throw $interpolateMinErr('badexpr', throw $interpolateMinErr('badexpr',
'Unexpected end of Angular expression. Expecting operator “{0}” at the end of the text “{1}”', 'Unexpected end of AngularJS expression. Expecting operator “{0}” at the end of the text “{1}”',
this.getEndOperator(innermostOperator), this.text); this.getEndOperator(innermostOperator), this.text);
} }
var operator = match[0]; var operator = match[0];
if (operator == "'" || operator == '"') { if (operator === '\'' || operator === '"') {
this.ruleStack.push(this.ruleInAngularExpression); this.ruleStack.push(this.ruleInAngularExpression);
this.startStringAtMatch(match); this.startStringAtMatch(match);
return; return;
} }
if (operator == ",") { if (operator === ',') {
if (this.trustedContext) { if (this.trustedContext) {
position = indexToLineAndColumn(this.text, this.index); position = indexToLineAndColumn(this.text, this.index);
throw $interpolateMinErr('unsafe', throw $interpolateMinErr('unsafe',
@ -836,7 +822,7 @@ MessageFormatParser.prototype.ruleInAngularExpression = function ruleInAngularEx
this.errorInParseLogic(); this.errorInParseLogic();
} }
if (this.angularOperatorStack.length > 0) { if (this.angularOperatorStack.length > 0) {
if (beginOperator == this.angularOperatorStack[0]) { if (beginOperator === this.angularOperatorStack[0]) {
this.angularOperatorStack.shift(); this.angularOperatorStack.shift();
return; return;
} }
@ -864,7 +850,6 @@ MessageFormatParser.prototype.ruleInAngularExpression = function ruleInAngularEx
/* global noop: true */ /* global noop: true */
/* global toJson: true */ /* global toJson: true */
/* global MessageFormatParser: false */ /* global MessageFormatParser: false */
/* global stringify: false */
/** /**
* @ngdoc module * @ngdoc module
@ -875,7 +860,7 @@ MessageFormatParser.prototype.ruleInAngularExpression = function ruleInAngularEx
* *
* ## What is ngMessageFormat? * ## What is ngMessageFormat?
* *
* The ngMessageFormat module extends the Angular {@link ng.$interpolate `$interpolate`} service * The ngMessageFormat module extends the AngularJS {@link ng.$interpolate `$interpolate`} service
* with a syntax for handling pluralization and gender specific messages, which is based on the * with a syntax for handling pluralization and gender specific messages, which is based on the
* [ICU MessageFormat syntax][ICU]. * [ICU MessageFormat syntax][ICU].
* *
@ -909,9 +894,9 @@ MessageFormatParser.prototype.ruleInAngularExpression = function ruleInAngularEx
* this.gender = gender; * this.gender = gender;
* } * }
* *
* var alice = new Person("Alice", "female"), * var alice = new Person('Alice', 'female'),
* bob = new Person("Bob", "male"), * bob = new Person('Bob', 'male'),
* ashley = new Person("Ashley", ""); * ashley = new Person('Ashley', '');
* *
* angular.module('msgFmtExample', ['ngMessageFormat']) * angular.module('msgFmtExample', ['ngMessageFormat'])
* .controller('AppController', ['$scope', function($scope) { * .controller('AppController', ['$scope', function($scope) {
@ -952,11 +937,11 @@ MessageFormatParser.prototype.ruleInAngularExpression = function ruleInAngularEx
* this.gender = gender; * this.gender = gender;
* } * }
* *
* var alice = new Person("Alice", "female"), * var alice = new Person('Alice', 'female'),
* bob = new Person("Bob", "male"), * bob = new Person('Bob', 'male'),
* sarah = new Person("Sarah", "female"), * sarah = new Person('Sarah', 'female'),
* harry = new Person("Harry Potter", "male"), * harry = new Person('Harry Potter', 'male'),
* ashley = new Person("Ashley", ""); * ashley = new Person('Ashley', '');
* *
* angular.module('msgFmtExample', ['ngMessageFormat']) * angular.module('msgFmtExample', ['ngMessageFormat'])
* .controller('AppController', ['$scope', function($scope) { * .controller('AppController', ['$scope', function($scope) {
@ -1012,10 +997,10 @@ MessageFormatParser.prototype.ruleInAngularExpression = function ruleInAngularEx
* this.gender = gender; * this.gender = gender;
* } * }
* *
* var alice = new Person("Alice", "female"), * var alice = new Person('Alice', 'female'),
* bob = new Person("Bob", "male"), * bob = new Person('Bob', 'male'),
* harry = new Person("Harry Potter", "male"), * harry = new Person('Harry Potter', 'male'),
* ashley = new Person("Ashley", ""); * ashley = new Person('Ashley', '');
* *
* angular.module('msgFmtExample', ['ngMessageFormat']) * angular.module('msgFmtExample', ['ngMessageFormat'])
* .controller('AppController', ['$scope', function($scope) { * .controller('AppController', ['$scope', function($scope) {
@ -1028,13 +1013,13 @@ MessageFormatParser.prototype.ruleInAngularExpression = function ruleInAngularEx
*/ */
var $$MessageFormatFactory = ['$parse', '$locale', '$sce', '$exceptionHandler', function $$messageFormat( var $$MessageFormatFactory = ['$parse', '$locale', '$sce', '$exceptionHandler', function $$messageFormat(
$parse, $locale, $sce, $exceptionHandler) { $parse, $locale, $sce, $exceptionHandler) {
function getStringifier(trustedContext, allOrNothing, text) { function getStringifier(trustedContext, allOrNothing, text) {
return function stringifier(value) { return function stringifier(value) {
try { try {
value = trustedContext ? $sce['getTrusted'](trustedContext, value) : $sce['valueOf'](value); value = trustedContext ? $sce['getTrusted'](trustedContext, value) : $sce['valueOf'](value);
return allOrNothing && (value === void 0) ? value : stringify(value); return allOrNothing && (value === undefined) ? value : $$stringify(value);
} catch (err) { } catch (err) {
$exceptionHandler($interpolateMinErr['interr'](text, err)); $exceptionHandler($interpolateMinErr['interr'](text, err));
} }
@ -1055,7 +1040,7 @@ var $$MessageFormatFactory = ['$parse', '$locale', '$sce', '$exceptionHandler',
}]; }];
var $$interpolateDecorator = ['$$messageFormat', '$delegate', function $$interpolateDecorator($$messageFormat, $interpolate) { var $$interpolateDecorator = ['$$messageFormat', '$delegate', function $$interpolateDecorator($$messageFormat, $interpolate) {
if ($interpolate['startSymbol']() != "{{" || $interpolate['endSymbol']() != "}}") { if ($interpolate['startSymbol']() !== '{{' || $interpolate['endSymbol']() !== '}}') {
throw $interpolateMinErr('nochgmustache', 'angular-message-format.js currently does not allow you to use custom start and end symbols for interpolation.'); throw $interpolateMinErr('nochgmustache', 'angular-message-format.js currently does not allow you to use custom start and end symbols for interpolation.');
} }
var interpolate = $$messageFormat['interpolate']; var interpolate = $$messageFormat['interpolate'];
@ -1068,14 +1053,17 @@ var $interpolateMinErr;
var isFunction; var isFunction;
var noop; var noop;
var toJson; var toJson;
var $$stringify;
var module = window['angular']['module']('ngMessageFormat', ['ng']); var ngModule = window['angular']['module']('ngMessageFormat', ['ng']);
module['factory']('$$messageFormat', $$MessageFormatFactory); ngModule['info']({ 'angularVersion': '"1.8.2"' });
module['config'](['$provide', function($provide) { ngModule['factory']('$$messageFormat', $$MessageFormatFactory);
ngModule['config'](['$provide', function($provide) {
$interpolateMinErr = window['angular']['$interpolateMinErr']; $interpolateMinErr = window['angular']['$interpolateMinErr'];
isFunction = window['angular']['isFunction']; isFunction = window['angular']['isFunction'];
noop = window['angular']['noop']; noop = window['angular']['noop'];
toJson = window['angular']['toJson']; toJson = window['angular']['toJson'];
$$stringify = window['angular']['$$stringify'];
$provide['decorator']('$interpolate', $$interpolateDecorator); $provide['decorator']('$interpolate', $$interpolateDecorator);
}]); }]);

View File

@ -1,6 +1,6 @@
/** /**
* @license AngularJS v1.5.8 * @license AngularJS v1.8.2
* (c) 2010-2016 Google, Inc. http://angularjs.org * (c) 2010-2020 Google, Inc. http://angularjs.org
* License: MIT * License: MIT
*/ */
(function(window, angular) {'use strict'; (function(window, angular) {'use strict';
@ -23,9 +23,9 @@ var jqLite;
* sequencing based on the order of how the messages are defined in the template. * sequencing based on the order of how the messages are defined in the template.
* *
* Currently, the ngMessages module only contains the code for the `ngMessages`, `ngMessagesInclude` * Currently, the ngMessages module only contains the code for the `ngMessages`, `ngMessagesInclude`
* `ngMessage` and `ngMessageExp` directives. * `ngMessage`, `ngMessageExp` and `ngMessageDefault` directives.
* *
* # Usage * ## Usage
* The `ngMessages` directive allows keys in a key/value collection to be associated with a child element * The `ngMessages` directive allows keys in a key/value collection to be associated with a child element
* (or 'message') that will show or hide based on the truthiness of that key's value in the collection. A common use * (or 'message') that will show or hide based on the truthiness of that key's value in the collection. A common use
* case for `ngMessages` is to display error messages for inputs using the `$error` object exposed by the * case for `ngMessages` is to display error messages for inputs using the `$error` object exposed by the
@ -69,7 +69,7 @@ var jqLite;
* By default, `ngMessages` will only display one message for a particular key/value collection at any time. If more * By default, `ngMessages` will only display one message for a particular key/value collection at any time. If more
* than one message (or error) key is currently true, then which message is shown is determined by the order of messages * than one message (or error) key is currently true, then which message is shown is determined by the order of messages
* in the HTML template code (messages declared first are prioritised). This mechanism means the developer does not have * in the HTML template code (messages declared first are prioritised). This mechanism means the developer does not have
* to prioritise messages using custom JavaScript code. * to prioritize messages using custom JavaScript code.
* *
* Given the following error object for our example (which informs us that the field `myField` currently has both the * Given the following error object for our example (which informs us that the field `myField` currently has both the
* `required` and `email` errors): * `required` and `email` errors):
@ -200,7 +200,7 @@ var jqLite;
* *
* Feel free to use other structural directives such as ng-if and ng-switch to further control * Feel free to use other structural directives such as ng-if and ng-switch to further control
* what messages are active and when. Be careful, if you place ng-message on the same element * what messages are active and when. Be careful, if you place ng-message on the same element
* as these structural directives, Angular may not be able to determine if a message is active * as these structural directives, AngularJS may not be able to determine if a message is active
* or not. Therefore it is best to place the ng-message on a child element of the structural * or not. Therefore it is best to place the ng-message on a child element of the structural
* directive. * directive.
* *
@ -262,16 +262,36 @@ var jqLite;
* .some-message.ng-leave.ng-leave-active {} * .some-message.ng-leave.ng-leave-active {}
* ``` * ```
* *
* {@link ngAnimate Click here} to learn how to use JavaScript animations or to learn more about ngAnimate. * {@link ngAnimate See the ngAnimate docs} to learn how to use JavaScript animations or to learn
* more about ngAnimate.
*
* ## Displaying a default message
* If the ngMessages renders no inner ngMessage directive (i.e. when none of the truthy
* keys are matched by a defined message), then it will render a default message
* using the {@link ngMessageDefault} directive.
* Note that matched messages will always take precedence over unmatched messages. That means
* the default message will not be displayed when another message is matched. This is also
* true for `ng-messages-multiple`.
*
* ```html
* <div ng-messages="myForm.myField.$error" role="alert">
* <div ng-message="required">This field is required</div>
* <div ng-message="minlength">This field is too short</div>
* <div ng-message-default>This field has an input error</div>
* </div>
* ```
*
*/ */
angular.module('ngMessages', [], function initAngularHelpers() { angular.module('ngMessages', [], function initAngularHelpers() {
// Access helpers from angular core. // Access helpers from AngularJS core.
// Do it inside a `config` block to ensure `window.angular` is available. // Do it inside a `config` block to ensure `window.angular` is available.
forEach = angular.forEach; forEach = angular.forEach;
isArray = angular.isArray; isArray = angular.isArray;
isString = angular.isString; isString = angular.isString;
jqLite = angular.element; jqLite = angular.element;
}) })
.info({ angularVersion: '"1.8.2"' })
/** /**
* @ngdoc directive * @ngdoc directive
@ -290,8 +310,11 @@ angular.module('ngMessages', [], function initAngularHelpers() {
* at a time and this depends on the prioritization of the messages within the template. (This can * at a time and this depends on the prioritization of the messages within the template. (This can
* be changed by using the `ng-messages-multiple` or `multiple` attribute on the directive container.) * be changed by using the `ng-messages-multiple` or `multiple` attribute on the directive container.)
* *
* A remote template can also be used to promote message reusability and messages can also be * A remote template can also be used (With {@link ngMessagesInclude}) to promote message
* overridden. * reusability and messages can also be overridden.
*
* A default message can also be displayed when no `ngMessage` directive is inserted, using the
* {@link ngMessageDefault} directive.
* *
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`. * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
* *
@ -302,6 +325,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
* <ANY ng-message="stringValue">...</ANY> * <ANY ng-message="stringValue">...</ANY>
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY> * <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
* <ANY ng-message-exp="expressionValue">...</ANY> * <ANY ng-message-exp="expressionValue">...</ANY>
* <ANY ng-message-default>...</ANY>
* </ANY> * </ANY>
* *
* <!-- or by using element directives --> * <!-- or by using element directives -->
@ -309,10 +333,11 @@ angular.module('ngMessages', [], function initAngularHelpers() {
* <ng-message when="stringValue">...</ng-message> * <ng-message when="stringValue">...</ng-message>
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message> * <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
* <ng-message when-exp="expressionValue">...</ng-message> * <ng-message when-exp="expressionValue">...</ng-message>
* <ng-message-default>...</ng-message-default>
* </ng-messages> * </ng-messages>
* ``` * ```
* *
* @param {string} ngMessages an angular expression evaluating to a key/value object * @param {string} ngMessages an AngularJS expression evaluating to a key/value object
* (this is typically the $error object on an ngModel instance). * (this is typically the $error object on an ngModel instance).
* @param {string=} ngMessagesMultiple|multiple when set, all messages will be displayed with true * @param {string=} ngMessagesMultiple|multiple when set, all messages will be displayed with true
* *
@ -337,6 +362,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
* <div ng-message="required">You did not enter a field</div> * <div ng-message="required">You did not enter a field</div>
* <div ng-message="minlength">Your field is too short</div> * <div ng-message="minlength">Your field is too short</div>
* <div ng-message="maxlength">Your field is too long</div> * <div ng-message="maxlength">Your field is too long</div>
* <div ng-message-default>This field has an input error</div>
* </div> * </div>
* </form> * </form>
* </file> * </file>
@ -352,7 +378,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
return { return {
require: 'ngMessages', require: 'ngMessages',
restrict: 'AE', restrict: 'AE',
controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { controller: ['$element', '$scope', '$attrs', function NgMessagesCtrl($element, $scope, $attrs) {
var ctrl = this; var ctrl = this;
var latestKey = 0; var latestKey = 0;
var nextAttachId = 0; var nextAttachId = 0;
@ -374,6 +400,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
var unmatchedMessages = []; var unmatchedMessages = [];
var matchedKeys = {}; var matchedKeys = {};
var truthyKeys = 0;
var messageItem = ctrl.head; var messageItem = ctrl.head;
var messageFound = false; var messageFound = false;
var totalMessages = 0; var totalMessages = 0;
@ -386,13 +413,17 @@ angular.module('ngMessages', [], function initAngularHelpers() {
var messageUsed = false; var messageUsed = false;
if (!messageFound) { if (!messageFound) {
forEach(collection, function(value, key) { forEach(collection, function(value, key) {
if (!messageUsed && truthy(value) && messageCtrl.test(key)) { if (truthy(value) && !messageUsed) {
// this is to prevent the same error name from showing up twice truthyKeys++;
if (matchedKeys[key]) return;
matchedKeys[key] = true;
messageUsed = true; if (messageCtrl.test(key)) {
messageCtrl.attach(); // this is to prevent the same error name from showing up twice
if (matchedKeys[key]) return;
matchedKeys[key] = true;
messageUsed = true;
messageCtrl.attach();
}
} }
}); });
} }
@ -412,48 +443,60 @@ angular.module('ngMessages', [], function initAngularHelpers() {
messageCtrl.detach(); messageCtrl.detach();
}); });
unmatchedMessages.length !== totalMessages var messageMatched = unmatchedMessages.length !== totalMessages;
? $animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS) var attachDefault = ctrl.default && !messageMatched && truthyKeys > 0;
: $animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS);
if (attachDefault) {
ctrl.default.attach();
} else if (ctrl.default) {
ctrl.default.detach();
}
if (messageMatched || attachDefault) {
$animate.setClass($element, ACTIVE_CLASS, INACTIVE_CLASS);
} else {
$animate.setClass($element, INACTIVE_CLASS, ACTIVE_CLASS);
}
}; };
$scope.$watchCollection($attrs.ngMessages || $attrs['for'], ctrl.render); $scope.$watchCollection($attrs.ngMessages || $attrs['for'], ctrl.render);
// If the element is destroyed, proactively destroy all the currently visible messages
$element.on('$destroy', function() {
forEach(messages, function(item) {
item.message.detach();
});
});
this.reRender = function() { this.reRender = function() {
if (!renderLater) { if (!renderLater) {
renderLater = true; renderLater = true;
$scope.$evalAsync(function() { $scope.$evalAsync(function() {
if (renderLater) { if (renderLater && cachedCollection) {
cachedCollection && ctrl.render(cachedCollection); ctrl.render(cachedCollection);
} }
}); });
} }
}; };
this.register = function(comment, messageCtrl) { this.register = function(comment, messageCtrl, isDefault) {
var nextKey = latestKey.toString(); if (isDefault) {
messages[nextKey] = { ctrl.default = messageCtrl;
message: messageCtrl } else {
}; var nextKey = latestKey.toString();
insertMessageNode($element[0], comment, nextKey); messages[nextKey] = {
comment.$$ngMessageNode = nextKey; message: messageCtrl
latestKey++; };
insertMessageNode($element[0], comment, nextKey);
comment.$$ngMessageNode = nextKey;
latestKey++;
}
ctrl.reRender(); ctrl.reRender();
}; };
this.deregister = function(comment) { this.deregister = function(comment, isDefault) {
var key = comment.$$ngMessageNode; if (isDefault) {
delete comment.$$ngMessageNode; delete ctrl.default;
removeMessageNode($element[0], comment, key); } else {
delete messages[key]; var key = comment.$$ngMessageNode;
delete comment.$$ngMessageNode;
removeMessageNode($element[0], comment, key);
delete messages[key];
}
ctrl.reRender(); ctrl.reRender();
}; };
@ -500,6 +543,9 @@ angular.module('ngMessages', [], function initAngularHelpers() {
function removeMessageNode(parent, comment, key) { function removeMessageNode(parent, comment, key) {
var messageNode = messages[key]; var messageNode = messages[key];
// This message node may have already been removed by a call to deregister()
if (!messageNode) return;
var match = findPreviousMessage(parent, comment); var match = findPreviousMessage(parent, comment);
if (match) { if (match) {
match.next = messageNode.next; match.next = messageNode.next;
@ -594,6 +640,7 @@ angular.module('ngMessages', [], function initAngularHelpers() {
* @name ngMessage * @name ngMessage
* @restrict AE * @restrict AE
* @scope * @scope
* @priority 1
* *
* @description * @description
* `ngMessage` is a directive with the purpose to show and hide a particular message. * `ngMessage` is a directive with the purpose to show and hide a particular message.
@ -632,10 +679,8 @@ angular.module('ngMessages', [], function initAngularHelpers() {
* @scope * @scope
* *
* @description * @description
* `ngMessageExp` is a directive with the purpose to show and hide a particular message. * `ngMessageExp` is the same as {@link directive:ngMessage `ngMessage`}, but instead of a static
* For `ngMessageExp` to operate, a parent `ngMessages` directive on a parent DOM element * value, it accepts an expression to be evaluated for the message key.
* must be situated since it determines which messages are visible based on the state
* of the provided key/value map that `ngMessages` listens on.
* *
* @usage * @usage
* ```html * ```html
@ -654,9 +699,41 @@ angular.module('ngMessages', [], function initAngularHelpers() {
* *
* @param {expression} ngMessageExp|whenExp an expression value corresponding to the message key. * @param {expression} ngMessageExp|whenExp an expression value corresponding to the message key.
*/ */
.directive('ngMessageExp', ngMessageDirectiveFactory()); .directive('ngMessageExp', ngMessageDirectiveFactory())
function ngMessageDirectiveFactory() { /**
* @ngdoc directive
* @name ngMessageDefault
* @restrict AE
* @scope
*
* @description
* `ngMessageDefault` is a directive with the purpose to show and hide a default message for
* {@link directive:ngMessages}, when none of provided messages matches.
*
* More information about using `ngMessageDefault` can be found in the
* {@link module:ngMessages `ngMessages` module documentation}.
*
* @usage
* ```html
* <!-- using attribute directives -->
* <ANY ng-messages="expression" role="alert">
* <ANY ng-message="stringValue">...</ANY>
* <ANY ng-message="stringValue1, stringValue2, ...">...</ANY>
* <ANY ng-message-default>...</ANY>
* </ANY>
*
* <!-- or by using element directives -->
* <ng-messages for="expression" role="alert">
* <ng-message when="stringValue">...</ng-message>
* <ng-message when="stringValue1, stringValue2, ...">...</ng-message>
* <ng-message-default>...</ng-message-default>
* </ng-messages>
*
*/
.directive('ngMessageDefault', ngMessageDirectiveFactory(true));
function ngMessageDirectiveFactory(isDefault) {
return ['$animate', function($animate) { return ['$animate', function($animate) {
return { return {
restrict: 'AE', restrict: 'AE',
@ -665,25 +742,28 @@ function ngMessageDirectiveFactory() {
terminal: true, terminal: true,
require: '^^ngMessages', require: '^^ngMessages',
link: function(scope, element, attrs, ngMessagesCtrl, $transclude) { link: function(scope, element, attrs, ngMessagesCtrl, $transclude) {
var commentNode = element[0]; var commentNode, records, staticExp, dynamicExp;
var records; if (!isDefault) {
var staticExp = attrs.ngMessage || attrs.when; commentNode = element[0];
var dynamicExp = attrs.ngMessageExp || attrs.whenExp; staticExp = attrs.ngMessage || attrs.when;
var assignRecords = function(items) { dynamicExp = attrs.ngMessageExp || attrs.whenExp;
records = items
? (isArray(items)
? items
: items.split(/[\s,]+/))
: null;
ngMessagesCtrl.reRender();
};
if (dynamicExp) { var assignRecords = function(items) {
assignRecords(scope.$eval(dynamicExp)); records = items
scope.$watchCollection(dynamicExp, assignRecords); ? (isArray(items)
} else { ? items
assignRecords(staticExp); : items.split(/[\s,]+/))
: null;
ngMessagesCtrl.reRender();
};
if (dynamicExp) {
assignRecords(scope.$eval(dynamicExp));
scope.$watchCollection(dynamicExp, assignRecords);
} else {
assignRecords(staticExp);
}
} }
var currentElement, messageCtrl; var currentElement, messageCtrl;
@ -705,8 +785,10 @@ function ngMessageDirectiveFactory() {
// by another structural directive then it's time // by another structural directive then it's time
// to deregister the message from the controller // to deregister the message from the controller
currentElement.on('$destroy', function() { currentElement.on('$destroy', function() {
// If the message element was removed via a call to `detach` then `currentElement` will be null
// So this handler only handles cases where something else removed the message element.
if (currentElement && currentElement.$$attachId === $$attachId) { if (currentElement && currentElement.$$attachId === $$attachId) {
ngMessagesCtrl.deregister(commentNode); ngMessagesCtrl.deregister(commentNode, isDefault);
messageCtrl.detach(); messageCtrl.detach();
} }
newScope.$destroy(); newScope.$destroy();
@ -721,6 +803,14 @@ function ngMessageDirectiveFactory() {
$animate.leave(elm); $animate.leave(elm);
} }
} }
}, isDefault);
// We need to ensure that this directive deregisters itself when it no longer exists
// Normally this is done when the attached element is destroyed; but if this directive
// gets removed before we attach the message to the DOM there is nothing to watch
// in which case we must deregister when the containing scope is destroyed.
scope.$on('$destroy', function() {
ngMessagesCtrl.deregister(commentNode, isDefault);
}); });
} }
}; };

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
/** /**
* @license AngularJS v1.5.8 * @license AngularJS v1.8.2
* (c) 2010-2016 Google, Inc. http://angularjs.org * (c) 2010-2020 Google, Inc. http://angularjs.org
* License: MIT * License: MIT
*/ */
(function(window, angular) {'use strict'; (function(window, angular) {'use strict';
@ -1223,24 +1223,27 @@ function IDC_Y(cp) {
return false; return false;
} }
/* eslint-disable new-cap */
/** /**
* @ngdoc module * @ngdoc module
* @name ngParseExt * @name ngParseExt
* @packageName angular-parse-ext * @packageName angular-parse-ext
*
* @description * @description
* *
* # ngParseExt
*
* The `ngParseExt` module provides functionality to allow Unicode characters in * The `ngParseExt` module provides functionality to allow Unicode characters in
* identifiers inside Angular expressions. * identifiers inside AngularJS expressions.
*
*
* <div doc-module-components="ngParseExt"></div>
* *
* This module allows the usage of any identifier that follows ES6 identifier naming convention * This module allows the usage of any identifier that follows ES6 identifier naming convention
* to be used as an identifier in an Angular expression. ES6 delegates some of the identifier * to be used as an identifier in an AngularJS expression. ES6 delegates some of the identifier
* rules definition to Unicode, this module uses ES6 and Unicode 8.0 identifiers convention. * rules definition to Unicode, this module uses ES6 and Unicode 8.0 identifiers convention.
* *
* <div class="alert alert-warning">
* You cannot use Unicode characters for variable names in the {@link ngRepeat} or {@link ngOptions}
* expressions (e.g. `ng-repeat="f in поля"`), because even with `ngParseExt` included, these
* special expressions are not parsed by the {@link $parse} service.
* </div>
*/ */
/* global angularParseExtModule: true, /* global angularParseExtModule: true,
@ -1265,7 +1268,8 @@ function isValidIdentifierContinue(ch, cp) {
angular.module('ngParseExt', []) angular.module('ngParseExt', [])
.config(['$parseProvider', function($parseProvider) { .config(['$parseProvider', function($parseProvider) {
$parseProvider.setIdentifierFns(isValidIdentifierStart, isValidIdentifierContinue); $parseProvider.setIdentifierFns(isValidIdentifierStart, isValidIdentifierContinue);
}]); }])
.info({ angularVersion: '"1.8.2"' });
})(window, window.angular); })(window, window.angular);

View File

@ -1,6 +1,6 @@
/** /**
* @license AngularJS v1.5.8 * @license AngularJS v1.8.2
* (c) 2010-2016 Google, Inc. http://angularjs.org * (c) 2010-2020 Google, Inc. http://angularjs.org
* License: MIT * License: MIT
*/ */
(function(window, angular) {'use strict'; (function(window, angular) {'use strict';
@ -53,14 +53,9 @@ function shallowClearAndCopy(src, dst) {
* @name ngResource * @name ngResource
* @description * @description
* *
* # ngResource
*
* The `ngResource` module provides interaction support with RESTful services * The `ngResource` module provides interaction support with RESTful services
* via the $resource service. * via the $resource service.
* *
*
* <div doc-module-components="ngResource"></div>
*
* See {@link ngResource.$resourceProvider} and {@link ngResource.$resource} for usage. * See {@link ngResource.$resourceProvider} and {@link ngResource.$resource} for usage.
*/ */
@ -120,30 +115,35 @@ function shallowClearAndCopy(src, dst) {
* *
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
* `actions` methods. If a parameter value is a function, it will be called every time * `actions` methods. If a parameter value is a function, it will be called every time
* a param value needs to be obtained for a request (unless the param was overridden). The function * a param value needs to be obtained for a request (unless the param was overridden). The
* will be passed the current data value as an argument. * function will be passed the current data value as an argument.
* *
* Each key value in the parameter object is first bound to url template if present and then any * Each key value in the parameter object is first bound to url template if present and then any
* excess keys are appended to the url search query after the `?`. * excess keys are appended to the url search query after the `?`.
* *
* Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in * Given a template `/path/:verb` and parameter `{verb: 'greet', salutation: 'Hello'}` results in
* URL `/path/greet?salutation=Hello`. * URL `/path/greet?salutation=Hello`.
* *
* If the parameter value is prefixed with `@`, then the value for that parameter will be * If the parameter value is prefixed with `@`, then the value for that parameter will be
* extracted from the corresponding property on the `data` object (provided when calling a * extracted from the corresponding property on the `data` object (provided when calling actions
* "non-GET" action method). * with a request body).
* For example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of * For example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of
* `someParam` will be `data.someProp`. * `someParam` will be `data.someProp`.
* Note that the parameter will be ignored, when calling a "GET" action method (i.e. an action * Note that the parameter will be ignored, when calling a "GET" action method (i.e. an action
* method that does not accept a request body) * method that does not accept a request body).
* *
* @param {Object.<Object>=} actions Hash with declaration of custom actions that should extend * @param {Object.<Object>=} actions Hash with declaration of custom actions that will be available
* the default set of resource actions. The declaration should be created in the format of {@link * in addition to the default set of resource actions (see below). If a custom action has the same
* ng.$http#usage $http.config}: * key as a default action (e.g. `save`), then the default action will be *overwritten*, and not
* extended.
* *
* {action1: {method:?, params:?, isArray:?, headers:?, ...}, * The declaration should be created in the format of {@link ng.$http#usage $http.config}:
* action2: {method:?, params:?, isArray:?, headers:?, ...}, *
* ...} * {
* action1: {method:?, params:?, isArray:?, headers:?, ...},
* action2: {method:?, params:?, isArray:?, headers:?, ...},
* ...
* }
* *
* Where: * Where:
* *
@ -155,46 +155,58 @@ function shallowClearAndCopy(src, dst) {
* the parameter value is a function, it will be called every time when a param value needs to * the parameter value is a function, it will be called every time when a param value needs to
* be obtained for a request (unless the param was overridden). The function will be passed the * be obtained for a request (unless the param was overridden). The function will be passed the
* current data value as an argument. * current data value as an argument.
* - **`url`** {string} action specific `url` override. The url templating is supported just * - **`url`** {string} Action specific `url` override. The url templating is supported just
* like for the resource-level urls. * like for the resource-level urls.
* - **`isArray`** {boolean=} If true then the returned object for this action is an array, * - **`isArray`** {boolean=} If true then the returned object for this action is an array,
* see `returns` section. * see `returns` section.
* - **`transformRequest`** * - **`transformRequest`**
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}`
* transform function or an array of such functions. The transform function takes the http * Transform function or an array of such functions. The transform function takes the http
* request body and headers and returns its transformed (typically serialized) version. * request body and headers and returns its transformed (typically serialized) version.
* By default, transformRequest will contain one function that checks if the request data is * By default, transformRequest will contain one function that checks if the request data is
* an object and serializes to using `angular.toJson`. To prevent this behavior, set * an object and serializes it using `angular.toJson`. To prevent this behavior, set
* `transformRequest` to an empty array: `transformRequest: []` * `transformRequest` to an empty array: `transformRequest: []`
* - **`transformResponse`** * - **`transformResponse`**
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}`
* transform function or an array of such functions. The transform function takes the http * Transform function or an array of such functions. The transform function takes the HTTP
* response body and headers and returns its transformed (typically deserialized) version. * response body, headers and status and returns its transformed (typically deserialized)
* version.
* By default, transformResponse will contain one function that checks if the response looks * By default, transformResponse will contain one function that checks if the response looks
* like a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior, * like a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior,
* set `transformResponse` to an empty array: `transformResponse: []` * set `transformResponse` to an empty array: `transformResponse: []`
* - **`cache`** `{boolean|Cache}` If true, a default $http cache will be used to cache the * - **`cache`** `{boolean|Cache}` A boolean value or object created with
* GET request, otherwise if a cache instance built with * {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of the HTTP response.
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for * See {@link $http#caching $http Caching} for more information.
* caching. * - **`timeout`** `{number}` Timeout in milliseconds.<br />
* - **`timeout`** `{number}` timeout in milliseconds.<br />
* **Note:** In contrast to {@link ng.$http#usage $http.config}, {@link ng.$q promises} are * **Note:** In contrast to {@link ng.$http#usage $http.config}, {@link ng.$q promises} are
* **not** supported in $resource, because the same value would be used for multiple requests. * **not** supported in `$resource`, because the same value would be used for multiple requests.
* If you are looking for a way to cancel requests, you should use the `cancellable` option. * If you are looking for a way to cancel requests, you should use the `cancellable` option.
* - **`cancellable`** `{boolean}` if set to true, the request made by a "non-instance" call * - **`cancellable`** `{boolean}` If true, the request made by a "non-instance" call will be
* will be cancelled (if not already completed) by calling `$cancelRequest()` on the call's * cancelled (if not already completed) by calling `$cancelRequest()` on the call's return
* return value. Calling `$cancelRequest()` for a non-cancellable or an already * value. Calling `$cancelRequest()` for a non-cancellable or an already completed/cancelled
* completed/cancelled request will have no effect.<br /> * request will have no effect.
* - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the * - **`withCredentials`** `{boolean}` Whether to set the `withCredentials` flag on the
* XHR object. See * XHR object. See
* [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5) * [XMLHttpRequest.withCredentials](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials)
* for more information. * for more information.
* - **`responseType`** - `{string}` - see * - **`responseType`** `{string}` See
* [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType).
* - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods - * - **`interceptor`** `{Object=}` The interceptor object has four optional methods -
* `response` and `responseError`. Both `response` and `responseError` interceptors get called * `request`, `requestError`, `response`, and `responseError`. See
* with `http response` object. See {@link ng.$http $http interceptors}. * {@link ng.$http#interceptors $http interceptors} for details. Note that
* * `request`/`requestError` interceptors are applied before calling `$http`, thus before any
* global `$http` interceptors. Also, rejecting or throwing an error inside the `request`
* interceptor will result in calling the `responseError` interceptor.
* The resource instance or collection is available on the `resource` property of the
* `http response` object passed to `response`/`responseError` interceptors.
* Keep in mind that the associated promise will be resolved with the value returned by the
* response interceptors. Make sure you return an appropriate value and not the `response`
* object passed as input. For reference, the default `response` interceptor (which gets applied
* if you don't specify a custom one) returns `response.resource`.<br />
* See {@link ngResource.$resource#using-interceptors below} for an example of using
* interceptors in `$resource`.
* - **`hasBody`** `{boolean}` If true, then the request will have a body.
* If not specified, then only POST, PUT and PATCH requests will have a body. *
* @param {Object} options Hash with custom settings that should extend the * @param {Object} options Hash with custom settings that should extend the
* default `$resourceProvider` behavior. The supported options are: * default `$resourceProvider` behavior. The supported options are:
* *
@ -207,27 +219,29 @@ function shallowClearAndCopy(src, dst) {
* @returns {Object} A resource "class" object with methods for the default set of resource actions * @returns {Object} A resource "class" object with methods for the default set of resource actions
* optionally extended with custom `actions`. The default set contains these actions: * optionally extended with custom `actions`. The default set contains these actions:
* ```js * ```js
* { 'get': {method:'GET'}, * {
* 'save': {method:'POST'}, * 'get': {method: 'GET'},
* 'query': {method:'GET', isArray:true}, * 'save': {method: 'POST'},
* 'remove': {method:'DELETE'}, * 'query': {method: 'GET', isArray: true},
* 'delete': {method:'DELETE'} }; * 'remove': {method: 'DELETE'},
* 'delete': {method: 'DELETE'}
* }
* ``` * ```
* *
* Calling these methods invoke an {@link ng.$http} with the specified http method, * Calling these methods invoke {@link ng.$http} with the specified http method, destination and
* destination and parameters. When the data is returned from the server then the object is an * parameters. When the data is returned from the server then the object is an instance of the
* instance of the resource class. The actions `save`, `remove` and `delete` are available on it * resource class. The actions `save`, `remove` and `delete` are available on it as methods with
* as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, * the `$` prefix. This allows you to easily perform CRUD operations (create, read, update,
* read, update, delete) on server-side data like this: * delete) on server-side data like this:
* ```js * ```js
* var User = $resource('/user/:userId', {userId:'@id'}); * var User = $resource('/user/:userId', {userId: '@id'});
* var user = User.get({userId:123}, function() { * User.get({userId: 123}).$promise.then(function(user) {
* user.abc = true; * user.abc = true;
* user.$save(); * user.$save();
* }); * });
* ``` * ```
* *
* It is important to realize that invoking a $resource object method immediately returns an * It is important to realize that invoking a `$resource` object method immediately returns an
* empty reference (object or array depending on `isArray`). Once the data is returned from the * empty reference (object or array depending on `isArray`). Once the data is returned from the
* server the existing reference is populated with the actual data. This is a useful trick since * server the existing reference is populated with the actual data. This is a useful trick since
* usually the resource is assigned to a model which is then rendered by the view. Having an empty * usually the resource is assigned to a model which is then rendered by the view. Having an empty
@ -238,37 +252,43 @@ function shallowClearAndCopy(src, dst) {
* The action methods on the class object or instance object can be invoked with the following * The action methods on the class object or instance object can be invoked with the following
* parameters: * parameters:
* *
* - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` * - "class" actions without a body: `Resource.action([parameters], [success], [error])`
* - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` * - "class" actions with a body: `Resource.action([parameters], postData, [success], [error])`
* - non-GET instance actions: `instance.$action([parameters], [success], [error])` * - instance actions: `instance.$action([parameters], [success], [error])`
* *
* *
* Success callback is called with (value, responseHeaders) arguments, where the value is * When calling instance methods, the instance itself is used as the request body (if the action
* the populated resource instance or collection object. The error callback is called * should have a body). By default, only actions using `POST`, `PUT` or `PATCH` have request
* with (httpResponse) argument. * bodies, but you can use the `hasBody` configuration option to specify whether an action
* should have a body or not (regardless of its HTTP method).
* *
* Class actions return empty instance (with additional properties below). *
* Instance actions return promise of the action. * Success callback is called with (value (Object|Array), responseHeaders (Function),
* status (number), statusText (string)) arguments, where `value` is the populated resource
* instance or collection object. The error callback is called with (httpResponse) argument.
*
* Class actions return an empty instance (with the additional properties listed below).
* Instance actions return a promise for the operation.
* *
* The Resource instances and collections have these additional properties: * The Resource instances and collections have these additional properties:
* *
* - `$promise`: the {@link ng.$q promise} of the original server interaction that created this * - `$promise`: The {@link ng.$q promise} of the original server interaction that created this
* instance or collection. * instance or collection.
* *
* On success, the promise is resolved with the same resource instance or collection object, * On success, the promise is resolved with the same resource instance or collection object,
* updated with data from server. This makes it easy to use in * updated with data from server. This makes it easy to use in the
* {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view * {@link ngRoute.$routeProvider `resolve` section of `$routeProvider.when()`} to defer view
* rendering until the resource(s) are loaded. * rendering until the resource(s) are loaded.
* *
* On failure, the promise is rejected with the {@link ng.$http http response} object, without * On failure, the promise is rejected with the {@link ng.$http http response} object.
* the `resource` property.
* *
* If an interceptor object was provided, the promise will instead be resolved with the value * If an interceptor object was provided, the promise will instead be resolved with the value
* returned by the interceptor. * returned by the response interceptor (on success) or responceError interceptor (on failure).
* *
* - `$resolved`: `true` after first server interaction is completed (either with success or * - `$resolved`: `true` after first server interaction is completed (either with success or
* rejection), `false` before that. Knowing if the Resource has been resolved is useful in * rejection), `false` before that. Knowing if the Resource has been resolved is useful in
* data-binding. * data-binding. If there is a response/responseError interceptor and it returns a promise,
* `$resolved` will wait for that too.
* *
* The Resource instances and collections have these additional methods: * The Resource instances and collections have these additional methods:
* *
@ -279,138 +299,145 @@ function shallowClearAndCopy(src, dst) {
* *
* - `toJSON`: It returns a simple object without any of the extra properties added as part of * - `toJSON`: It returns a simple object without any of the extra properties added as part of
* the Resource API. This object can be serialized through {@link angular.toJson} safely * the Resource API. This object can be serialized through {@link angular.toJson} safely
* without attaching Angular-specific fields. Notice that `JSON.stringify` (and * without attaching AngularJS-specific fields. Notice that `JSON.stringify` (and
* `angular.toJson`) automatically use this method when serializing a Resource instance * `angular.toJson`) automatically use this method when serializing a Resource instance
* (see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON()_behavior)). * (see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#toJSON%28%29_behavior)).
* *
* @example * @example
* *
* # Credit card resource * ### Basic usage
* *
* ```js ```js
// Define CreditCard class // Define a CreditCard class
var CreditCard = $resource('/user/:userId/card/:cardId', var CreditCard = $resource('/users/:userId/cards/:cardId',
{userId:123, cardId:'@id'}, { {userId: 123, cardId: '@id'}, {
charge: {method:'POST', params:{charge:true}} charge: {method: 'POST', params: {charge: true}}
}); });
// We can retrieve a collection from the server // We can retrieve a collection from the server
var cards = CreditCard.query(function() { var cards = CreditCard.query();
// GET: /user/123/card // GET: /users/123/cards
// server returns: [ {id:456, number:'1234', name:'Smith'} ]; // server returns: [{id: 456, number: '1234', name: 'Smith'}]
// Wait for the request to complete
cards.$promise.then(function() {
var card = cards[0]; var card = cards[0];
// each item is an instance of CreditCard
expect(card instanceof CreditCard).toEqual(true);
card.name = "J. Smith";
// non GET methods are mapped onto the instances
card.$save();
// POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
// server returns: {id:456, number:'1234', name: 'J. Smith'};
// our custom method is mapped as well. // Each item is an instance of CreditCard
card.$charge({amount:9.99}); expect(card instanceof CreditCard).toEqual(true);
// POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
// Non-GET methods are mapped onto the instances
card.name = 'J. Smith';
card.$save();
// POST: /users/123/cards/456 {id: 456, number: '1234', name: 'J. Smith'}
// server returns: {id: 456, number: '1234', name: 'J. Smith'}
// Our custom method is mapped as well (since it uses POST)
card.$charge({amount: 9.99});
// POST: /users/123/cards/456?amount=9.99&charge=true {id: 456, number: '1234', name: 'J. Smith'}
}); });
// we can create an instance as well // We can create an instance as well
var newCard = new CreditCard({number:'0123'}); var newCard = new CreditCard({number: '0123'});
newCard.name = "Mike Smith"; newCard.name = 'Mike Smith';
newCard.$save();
// POST: /user/123/card {number:'0123', name:'Mike Smith'} var savePromise = newCard.$save();
// server returns: {id:789, number:'0123', name: 'Mike Smith'}; // POST: /users/123/cards {number: '0123', name: 'Mike Smith'}
expect(newCard.id).toEqual(789); // server returns: {id: 789, number: '0123', name: 'Mike Smith'}
* ```
savePromise.then(function() {
// Once the promise is resolved, the created instance
// is populated with the data returned by the server
expect(newCard.id).toEqual(789);
});
```
* *
* The object returned from this function execution is a resource "class" which has "static" method * The object returned from a call to `$resource` is a resource "class" which has one "static"
* for each action in the definition. * method for each action in the definition.
* *
* Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and * Calling these methods invokes `$http` on the `url` template with the given HTTP `method`,
* `headers`. * `params` and `headers`.
* *
* @example * @example
* *
* # User resource * ### Accessing the response
* *
* When the data is returned from the server then the object is an instance of the resource type and * When the data is returned from the server then the object is an instance of the resource type and
* all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
* operations (create, read, update, delete) on server-side data. * operations (create, read, update, delete) on server-side data.
*
```js ```js
var User = $resource('/user/:userId', {userId:'@id'}); var User = $resource('/users/:userId', {userId: '@id'});
User.get({userId:123}, function(user) { User.get({userId: 123}).$promise.then(function(user) {
user.abc = true; user.abc = true;
user.$save(); user.$save();
}); });
``` ```
* *
* It's worth noting that the success callback for `get`, `query` and other methods gets passed * It's worth noting that the success callback for `get`, `query` and other methods gets called with
* in the response that came from the server as well as $http header getter function, so one * the resource instance (populated with the data that came from the server) as well as an `$http`
* could rewrite the above example and get access to http headers as: * header getter function, the HTTP status code and the response status text. So one could rewrite
* the above example and get access to HTTP headers as follows:
* *
```js ```js
var User = $resource('/user/:userId', {userId:'@id'}); var User = $resource('/users/:userId', {userId: '@id'});
User.get({userId:123}, function(user, getResponseHeaders){ User.get({userId: 123}, function(user, getResponseHeaders) {
user.abc = true; user.abc = true;
user.$save(function(user, putResponseHeaders) { user.$save(function(user, putResponseHeaders) {
//user => saved user object // `user` => saved `User` object
//putResponseHeaders => $http header getter // `putResponseHeaders` => `$http` header getter
}); });
}); });
``` ```
* *
* You can also access the raw `$http` promise via the `$promise` property on the object returned * @example
* *
``` * ### Creating custom actions
var User = $resource('/user/:userId', {userId:'@id'}); *
User.get({userId:123}) * In this example we create a custom method on our resource to make a PUT request:
.$promise.then(function(user) { *
$scope.user = user; ```js
}); var app = angular.module('app', ['ngResource']);
// Some APIs expect a PUT request in the format URL/object/ID
// Here we are creating an 'update' method
app.factory('Notes', ['$resource', function($resource) {
return $resource('/notes/:id', {id: '@id'}, {
update: {method: 'PUT'}
});
}]);
// In our controller we get the ID from the URL using `$location`
app.controller('NotesCtrl', ['$location', 'Notes', function($location, Notes) {
// First, retrieve the corresponding `Note` object from the server
// (Assuming a URL of the form `.../notes?id=XYZ`)
var noteId = $location.search().id;
var note = Notes.get({id: noteId});
note.$promise.then(function() {
note.content = 'Hello, world!';
// Now call `update` to save the changes on the server
Notes.update(note);
// This will PUT /notes/ID with the note object as the request payload
// Since `update` is a non-GET method, it will also be available on the instance
// (prefixed with `$`), so we could replace the `Note.update()` call with:
//note.$update();
});
}]);
``` ```
* *
* @example * @example
* *
* # Creating a custom 'PUT' request * ### Cancelling requests
*
* In this example we create a custom method on our resource to make a PUT request
* ```js
* var app = angular.module('app', ['ngResource', 'ngRoute']);
*
* // Some APIs expect a PUT request in the format URL/object/ID
* // Here we are creating an 'update' method
* app.factory('Notes', ['$resource', function($resource) {
* return $resource('/notes/:id', null,
* {
* 'update': { method:'PUT' }
* });
* }]);
*
* // In our controller we get the ID from the URL using ngRoute and $routeParams
* // We pass in $routeParams and our Notes factory along with $scope
* app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes',
function($scope, $routeParams, Notes) {
* // First get a note object from the factory
* var note = Notes.get({ id:$routeParams.id });
* $id = note.id;
*
* // Now call update passing in the ID first then the object you are updating
* Notes.update({ id:$id }, note);
*
* // This will PUT /notes/ID with the note object in the request payload
* }]);
* ```
*
* @example
*
* # Cancelling requests
* *
* If an action's configuration specifies that it is cancellable, you can cancel the request related * If an action's configuration specifies that it is cancellable, you can cancel the request related
* to an instance or collection (as long as it is a result of a "non-instance" call): * to an instance or collection (as long as it is a result of a "non-instance" call):
* *
```js ```js
// ...defining the `Hotel` resource... // ...defining the `Hotel` resource...
var Hotel = $resource('/api/hotel/:id', {id: '@id'}, { var Hotel = $resource('/api/hotels/:id', {id: '@id'}, {
// Let's make the `query()` method cancellable // Let's make the `query()` method cancellable
query: {method: 'get', isArray: true, cancellable: true} query: {method: 'get', isArray: true, cancellable: true}
}); });
@ -420,18 +447,60 @@ function shallowClearAndCopy(src, dst) {
this.onDestinationChanged = function onDestinationChanged(destination) { this.onDestinationChanged = function onDestinationChanged(destination) {
// We don't care about any pending request for hotels // We don't care about any pending request for hotels
// in a different destination any more // in a different destination any more
this.availableHotels.$cancelRequest(); if (this.availableHotels) {
this.availableHotels.$cancelRequest();
}
// Let's query for hotels in '<destination>' // Let's query for hotels in `destination`
// (calls: /api/hotel?location=<destination>) // (calls: /api/hotels?location=<destination>)
this.availableHotels = Hotel.query({location: destination}); this.availableHotels = Hotel.query({location: destination});
}; };
``` ```
* *
* @example
*
* ### Using interceptors
*
* You can use interceptors to transform the request or response, perform additional operations, and
* modify the returned instance/collection. The following example, uses `request` and `response`
* interceptors to augment the returned instance with additional info:
*
```js
var Thing = $resource('/api/things/:id', {id: '@id'}, {
save: {
method: 'POST',
interceptor: {
request: function(config) {
// Before the request is sent out, store a timestamp on the request config
config.requestTimestamp = Date.now();
return config;
},
response: function(response) {
// Get the instance from the response object
var instance = response.resource;
// Augment the instance with a custom `saveLatency` property, computed as the time
// between sending the request and receiving the response.
instance.saveLatency = Date.now() - response.config.requestTimestamp;
// Return the instance
return instance;
}
}
}
});
Thing.save({foo: 'bar'}).$promise.then(function(thing) {
console.log('That thing was saved in ' + thing.saveLatency + 'ms.');
});
```
*
*/ */
angular.module('ngResource', ['ng']). angular.module('ngResource', ['ng']).
provider('$resource', function() { info({ angularVersion: '"1.8.2"' }).
var PROTOCOL_AND_DOMAIN_REGEX = /^https?:\/\/[^\/]*/; provider('$resource', function ResourceProvider() {
var PROTOCOL_AND_IPV6_REGEX = /^https?:\/\/\[[^\]]*][^/]*/;
var provider = this; var provider = this;
/** /**
@ -475,11 +544,11 @@ angular.module('ngResource', ['ng']).
* ```js * ```js
* angular. * angular.
* module('myApp'). * module('myApp').
* config(['resourceProvider', function ($resourceProvider) { * config(['$resourceProvider', function ($resourceProvider) {
* $resourceProvider.defaults.actions.update = { * $resourceProvider.defaults.actions.update = {
* method: 'PUT' * method: 'PUT'
* }; * };
* }); * }]);
* ``` * ```
* *
* Or you can even overwrite the whole `actions` list and specify your own: * Or you can even overwrite the whole `actions` list and specify your own:
@ -487,9 +556,9 @@ angular.module('ngResource', ['ng']).
* ```js * ```js
* angular. * angular.
* module('myApp'). * module('myApp').
* config(['resourceProvider', function ($resourceProvider) { * config(['$resourceProvider', function ($resourceProvider) {
* $resourceProvider.defaults.actions = { * $resourceProvider.defaults.actions = {
* create: {method: 'POST'} * create: {method: 'POST'},
* get: {method: 'GET'}, * get: {method: 'GET'},
* getAll: {method: 'GET', isArray:true}, * getAll: {method: 'GET', isArray:true},
* update: {method: 'PUT'}, * update: {method: 'PUT'},
@ -519,49 +588,15 @@ angular.module('ngResource', ['ng']).
this.$get = ['$http', '$log', '$q', '$timeout', function($http, $log, $q, $timeout) { this.$get = ['$http', '$log', '$q', '$timeout', function($http, $log, $q, $timeout) {
var noop = angular.noop, var noop = angular.noop,
forEach = angular.forEach, forEach = angular.forEach,
extend = angular.extend, extend = angular.extend,
copy = angular.copy, copy = angular.copy,
isFunction = angular.isFunction; isArray = angular.isArray,
isDefined = angular.isDefined,
/** isFunction = angular.isFunction,
* We need our custom method because encodeURIComponent is too aggressive and doesn't follow isNumber = angular.isNumber,
* http://www.ietf.org/rfc/rfc3986.txt with regards to the character set encodeUriQuery = angular.$$encodeUriQuery,
* (pchar) allowed in path segments: encodeUriSegment = angular.$$encodeUriSegment;
* segment = *pchar
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
* pct-encoded = "%" HEXDIG HEXDIG
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
* / "*" / "+" / "," / ";" / "="
*/
function encodeUriSegment(val) {
return encodeUriQuery(val, true).
replace(/%26/gi, '&').
replace(/%3D/gi, '=').
replace(/%2B/gi, '+');
}
/**
* This method is intended for encoding *key* or *value* parts of query component. We need a
* custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't
* have to be encoded per http://tools.ietf.org/html/rfc3986:
* query = *( pchar / "/" / "?" )
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* pct-encoded = "%" HEXDIG HEXDIG
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
* / "*" / "+" / "," / ";" / "="
*/
function encodeUriQuery(val, pctEncodeSpaces) {
return encodeURIComponent(val).
replace(/%40/gi, '@').
replace(/%3A/gi, ':').
replace(/%24/g, '$').
replace(/%2C/gi, ',').
replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
}
function Route(template, defaults) { function Route(template, defaults) {
this.template = template; this.template = template;
@ -575,42 +610,42 @@ angular.module('ngResource', ['ng']).
url = actionUrl || self.template, url = actionUrl || self.template,
val, val,
encodedVal, encodedVal,
protocolAndDomain = ''; protocolAndIpv6 = '';
var urlParams = self.urlParams = {}; var urlParams = self.urlParams = Object.create(null);
forEach(url.split(/\W/), function(param) { forEach(url.split(/\W/), function(param) {
if (param === 'hasOwnProperty') { if (param === 'hasOwnProperty') {
throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name."); throw $resourceMinErr('badname', 'hasOwnProperty is not a valid parameter name.');
} }
if (!(new RegExp("^\\d+$").test(param)) && param && if (!(new RegExp('^\\d+$').test(param)) && param &&
(new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) { (new RegExp('(^|[^\\\\]):' + param + '(\\W|$)').test(url))) {
urlParams[param] = { urlParams[param] = {
isQueryParamValue: (new RegExp("\\?.*=:" + param + "(?:\\W|$)")).test(url) isQueryParamValue: (new RegExp('\\?.*=:' + param + '(?:\\W|$)')).test(url)
}; };
} }
}); });
url = url.replace(/\\:/g, ':'); url = url.replace(/\\:/g, ':');
url = url.replace(PROTOCOL_AND_DOMAIN_REGEX, function(match) { url = url.replace(PROTOCOL_AND_IPV6_REGEX, function(match) {
protocolAndDomain = match; protocolAndIpv6 = match;
return ''; return '';
}); });
params = params || {}; params = params || {};
forEach(self.urlParams, function(paramInfo, urlParam) { forEach(self.urlParams, function(paramInfo, urlParam) {
val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
if (angular.isDefined(val) && val !== null) { if (isDefined(val) && val !== null) {
if (paramInfo.isQueryParamValue) { if (paramInfo.isQueryParamValue) {
encodedVal = encodeUriQuery(val, true); encodedVal = encodeUriQuery(val, true);
} else { } else {
encodedVal = encodeUriSegment(val); encodedVal = encodeUriSegment(val);
} }
url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function(match, p1) { url = url.replace(new RegExp(':' + urlParam + '(\\W|$)', 'g'), function(match, p1) {
return encodedVal + p1; return encodedVal + p1;
}); });
} else { } else {
url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match, url = url.replace(new RegExp('(/?):' + urlParam + '(\\W|$)', 'g'), function(match,
leadingSlashes, tail) { leadingSlashes, tail) {
if (tail.charAt(0) == '/') { if (tail.charAt(0) === '/') {
return tail; return tail;
} else { } else {
return leadingSlashes + tail; return leadingSlashes + tail;
@ -624,11 +659,12 @@ angular.module('ngResource', ['ng']).
url = url.replace(/\/+$/, '') || '/'; url = url.replace(/\/+$/, '') || '/';
} }
// then replace collapse `/.` if found in the last URL path segment before the query // Collapse `/.` if found in the last URL path segment before the query.
// E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x` // E.g. `http://url.com/id/.format?q=x` becomes `http://url.com/id.format?q=x`.
url = url.replace(/\/\.(?=\w+($|\?))/, '.'); url = url.replace(/\/\.(?=\w+($|\?))/, '.');
// replace escaped `/\.` with `/.` // Replace escaped `/\.` with `/.`.
config.url = protocolAndDomain + url.replace(/\/\\\./, '/.'); // (If `\.` comes from a param value, it will be encoded as `%5C.`.)
config.url = protocolAndIpv6 + url.replace(/\/(\\|%5C)\./, '/.');
// set params - delegate param encoding to $http // set params - delegate param encoding to $http
@ -652,7 +688,7 @@ angular.module('ngResource', ['ng']).
actionParams = extend({}, paramDefaults, actionParams); actionParams = extend({}, paramDefaults, actionParams);
forEach(actionParams, function(value, key) { forEach(actionParams, function(value, key) {
if (isFunction(value)) { value = value(data); } if (isFunction(value)) { value = value(data); }
ids[key] = value && value.charAt && value.charAt(0) == '@' ? ids[key] = value && value.charAt && value.charAt(0) === '@' ?
lookupDottedPath(data, value.substr(1)) : value; lookupDottedPath(data, value.substr(1)) : value;
}); });
return ids; return ids;
@ -670,17 +706,17 @@ angular.module('ngResource', ['ng']).
var data = extend({}, this); var data = extend({}, this);
delete data.$promise; delete data.$promise;
delete data.$resolved; delete data.$resolved;
delete data.$cancelRequest;
return data; return data;
}; };
forEach(actions, function(action, name) { forEach(actions, function(action, name) {
var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method); var hasBody = action.hasBody === true || (action.hasBody !== false && /^(POST|PUT|PATCH)$/i.test(action.method));
var numericTimeout = action.timeout; var numericTimeout = action.timeout;
var cancellable = angular.isDefined(action.cancellable) ? action.cancellable : var cancellable = isDefined(action.cancellable) ?
(options && angular.isDefined(options.cancellable)) ? options.cancellable : action.cancellable : route.defaults.cancellable;
provider.defaults.cancellable;
if (numericTimeout && !angular.isNumber(numericTimeout)) { if (numericTimeout && !isNumber(numericTimeout)) {
$log.debug('ngResource:\n' + $log.debug('ngResource:\n' +
' Only numeric values are allowed as `timeout`.\n' + ' Only numeric values are allowed as `timeout`.\n' +
' Promises are not supported in $resource, because the same value would ' + ' Promises are not supported in $resource, because the same value would ' +
@ -691,54 +727,61 @@ angular.module('ngResource', ['ng']).
} }
Resource[name] = function(a1, a2, a3, a4) { Resource[name] = function(a1, a2, a3, a4) {
var params = {}, data, success, error; var params = {}, data, onSuccess, onError;
/* jshint -W086 */ /* (purposefully fall through case statements) */
switch (arguments.length) { switch (arguments.length) {
case 4: case 4:
error = a4; onError = a4;
success = a3; onSuccess = a3;
//fallthrough // falls through
case 3: case 3:
case 2: case 2:
if (isFunction(a2)) { if (isFunction(a2)) {
if (isFunction(a1)) { if (isFunction(a1)) {
success = a1; onSuccess = a1;
error = a2; onError = a2;
break; break;
} }
success = a2; onSuccess = a2;
error = a3; onError = a3;
//fallthrough // falls through
} else { } else {
params = a1; params = a1;
data = a2; data = a2;
success = a3; onSuccess = a3;
break; break;
} }
// falls through
case 1: case 1:
if (isFunction(a1)) success = a1; if (isFunction(a1)) onSuccess = a1;
else if (hasBody) data = a1; else if (hasBody) data = a1;
else params = a1; else params = a1;
break; break;
case 0: break; case 0: break;
default: default:
throw $resourceMinErr('badargs', throw $resourceMinErr('badargs',
"Expected up to 4 arguments [params, data, success, error], got {0} arguments", 'Expected up to 4 arguments [params, data, success, error], got {0} arguments',
arguments.length); arguments.length);
} }
/* jshint +W086 */ /* (purposefully fall through case statements) */
var isInstanceCall = this instanceof Resource; var isInstanceCall = this instanceof Resource;
var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data)); var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data));
var httpConfig = {}; var httpConfig = {};
var requestInterceptor = action.interceptor && action.interceptor.request || undefined;
var requestErrorInterceptor = action.interceptor && action.interceptor.requestError ||
undefined;
var responseInterceptor = action.interceptor && action.interceptor.response || var responseInterceptor = action.interceptor && action.interceptor.response ||
defaultResponseInterceptor; defaultResponseInterceptor;
var responseErrorInterceptor = action.interceptor && action.interceptor.responseError || var responseErrorInterceptor = action.interceptor && action.interceptor.responseError ||
undefined; $q.reject;
var successCallback = onSuccess ? function(val) {
onSuccess(val, response.headers, response.status, response.statusText);
} : undefined;
var errorCallback = onError || undefined;
var timeoutDeferred; var timeoutDeferred;
var numericTimeoutPromise; var numericTimeoutPromise;
var response;
forEach(action, function(value, key) { forEach(action, function(value, key) {
switch (key) { switch (key) {
@ -767,23 +810,28 @@ angular.module('ngResource', ['ng']).
extend({}, extractParams(data, action.params || {}), params), extend({}, extractParams(data, action.params || {}), params),
action.url); action.url);
var promise = $http(httpConfig).then(function(response) { // Start the promise chain
var data = response.data; var promise = $q.
resolve(httpConfig).
then(requestInterceptor).
catch(requestErrorInterceptor).
then($http);
promise = promise.then(function(resp) {
var data = resp.data;
if (data) { if (data) {
// Need to convert action.isArray to boolean in case it is undefined // Need to convert action.isArray to boolean in case it is undefined
// jshint -W018 if (isArray(data) !== (!!action.isArray)) {
if (angular.isArray(data) !== (!!action.isArray)) {
throw $resourceMinErr('badcfg', throw $resourceMinErr('badcfg',
'Error in resource configuration for action `{0}`. Expected response to ' + 'Error in resource configuration for action `{0}`. Expected response to ' +
'contain an {1} but got an {2} (Request: {3} {4})', name, action.isArray ? 'array' : 'object', 'contain an {1} but got an {2} (Request: {3} {4})', name, action.isArray ? 'array' : 'object',
angular.isArray(data) ? 'array' : 'object', httpConfig.method, httpConfig.url); isArray(data) ? 'array' : 'object', httpConfig.method, httpConfig.url);
} }
// jshint +W018
if (action.isArray) { if (action.isArray) {
value.length = 0; value.length = 0;
forEach(data, function(item) { forEach(data, function(item) {
if (typeof item === "object") { if (typeof item === 'object') {
value.push(new Resource(item)); value.push(new Resource(item));
} else { } else {
// Valid JSON values may be string literals, and these should not be converted // Valid JSON values may be string literals, and these should not be converted
@ -798,30 +846,27 @@ angular.module('ngResource', ['ng']).
value.$promise = promise; // Restore the promise value.$promise = promise; // Restore the promise
} }
} }
response.resource = value;
return response; resp.resource = value;
}, function(response) { response = resp;
(error || noop)(response); return responseInterceptor(resp);
return $q.reject(response); }, function(rejectionOrResponse) {
rejectionOrResponse.resource = value;
response = rejectionOrResponse;
return responseErrorInterceptor(rejectionOrResponse);
}); });
promise['finally'](function() { promise = promise['finally'](function() {
value.$resolved = true; value.$resolved = true;
if (!isInstanceCall && cancellable) { if (!isInstanceCall && cancellable) {
value.$cancelRequest = angular.noop; value.$cancelRequest = noop;
$timeout.cancel(numericTimeoutPromise); $timeout.cancel(numericTimeoutPromise);
timeoutDeferred = numericTimeoutPromise = httpConfig.timeout = null; timeoutDeferred = numericTimeoutPromise = httpConfig.timeout = null;
} }
}); });
promise = promise.then( // Run the `success`/`error` callbacks, but do not let them affect the returned promise.
function(response) { promise.then(successCallback, errorCallback);
var value = responseInterceptor(response);
(success || noop)(value, response.headers);
return value;
},
responseErrorInterceptor);
if (!isInstanceCall) { if (!isInstanceCall) {
// we are creating instance / collection // we are creating instance / collection
@ -829,13 +874,20 @@ angular.module('ngResource', ['ng']).
// - return the instance / collection // - return the instance / collection
value.$promise = promise; value.$promise = promise;
value.$resolved = false; value.$resolved = false;
if (cancellable) value.$cancelRequest = timeoutDeferred.resolve; if (cancellable) value.$cancelRequest = cancelRequest;
return value; return value;
} }
// instance call // instance call
return promise; return promise;
function cancelRequest(value) {
promise.catch(noop);
if (timeoutDeferred !== null) {
timeoutDeferred.resolve(value);
}
}
}; };
@ -848,10 +900,6 @@ angular.module('ngResource', ['ng']).
}; };
}); });
Resource.bind = function(additionalParamDefaults) {
return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
};
return Resource; return Resource;
} }

View File

@ -1,6 +1,6 @@
/** /**
* @license AngularJS v1.5.8 * @license AngularJS v1.8.2
* (c) 2010-2016 Google, Inc. http://angularjs.org * (c) 2010-2020 Google, Inc. http://angularjs.org
* License: MIT * License: MIT
*/ */
(function(window, angular) {'use strict'; (function(window, angular) {'use strict';
@ -32,43 +32,51 @@ function shallowCopy(src, dst) {
return dst || src; return dst || src;
} }
/* global routeToRegExp: false */
/* global shallowCopy: false */ /* global shallowCopy: false */
// There are necessary for `shallowCopy()` (included via `src/shallowCopy.js`). // `isArray` and `isObject` are necessary for `shallowCopy()` (included via `src/shallowCopy.js`).
// They are initialized inside the `$RouteProvider`, to ensure `window.angular` is available. // They are initialized inside the `$RouteProvider`, to ensure `window.angular` is available.
var isArray; var isArray;
var isObject; var isObject;
var isDefined;
var noop;
/** /**
* @ngdoc module * @ngdoc module
* @name ngRoute * @name ngRoute
* @description * @description
* *
* # ngRoute * The `ngRoute` module provides routing and deeplinking services and directives for AngularJS apps.
*
* The `ngRoute` module provides routing and deeplinking services and directives for angular apps.
* *
* ## Example * ## Example
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. * See {@link ngRoute.$route#examples $route} for an example of configuring and using `ngRoute`.
* *
*
* <div doc-module-components="ngRoute"></div>
*/ */
/* global -ngRouteModule */ /* global -ngRouteModule */
var ngRouteModule = angular.module('ngRoute', ['ng']). var ngRouteModule = angular.
provider('$route', $RouteProvider), module('ngRoute', []).
$routeMinErr = angular.$$minErr('ngRoute'); info({ angularVersion: '"1.8.2"' }).
provider('$route', $RouteProvider).
// Ensure `$route` will be instantiated in time to capture the initial `$locationChangeSuccess`
// event (unless explicitly disabled). This is necessary in case `ngView` is included in an
// asynchronously loaded template.
run(instantiateRoute);
var $routeMinErr = angular.$$minErr('ngRoute');
var isEagerInstantiationEnabled;
/** /**
* @ngdoc provider * @ngdoc provider
* @name $routeProvider * @name $routeProvider
* @this
* *
* @description * @description
* *
* Used for configuring routes. * Used for configuring routes.
* *
* ## Example * ## Example
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. * See {@link ngRoute.$route#examples $route} for an example of configuring and using `ngRoute`.
* *
* ## Dependencies * ## Dependencies
* Requires the {@link ngRoute `ngRoute`} module to be installed. * Requires the {@link ngRoute `ngRoute`} module to be installed.
@ -76,6 +84,8 @@ var ngRouteModule = angular.module('ngRoute', ['ng']).
function $RouteProvider() { function $RouteProvider() {
isArray = angular.isArray; isArray = angular.isArray;
isObject = angular.isObject; isObject = angular.isObject;
isDefined = angular.isDefined;
noop = angular.noop;
function inherit(parent, extra) { function inherit(parent, extra) {
return angular.extend(Object.create(parent), extra); return angular.extend(Object.create(parent), extra);
@ -112,12 +122,12 @@ function $RouteProvider() {
* *
* Object properties: * Object properties:
* *
* - `controller` `{(string|function()=}` Controller fn that should be associated with * - `controller` `{(string|Function)=}` Controller fn that should be associated with
* newly created scope or the name of a {@link angular.Module#controller registered * newly created scope or the name of a {@link angular.Module#controller registered
* controller} if passed as a string. * controller} if passed as a string.
* - `controllerAs` `{string=}` An identifier name for a reference to the controller. * - `controllerAs` `{string=}` An identifier name for a reference to the controller.
* If present, the controller will be published to scope under the `controllerAs` name. * If present, the controller will be published to scope under the `controllerAs` name.
* - `template` `{string=|function()=}` html template as a string or a function that * - `template` `{(string|Function)=}` html template as a string or a function that
* returns an html template as a string which should be used by {@link * returns an html template as a string which should be used by {@link
* ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. * ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives.
* This property takes precedence over `templateUrl`. * This property takes precedence over `templateUrl`.
@ -127,7 +137,9 @@ function $RouteProvider() {
* - `{Array.<Object>}` - route parameters extracted from the current * - `{Array.<Object>}` - route parameters extracted from the current
* `$location.path()` by applying the current route * `$location.path()` by applying the current route
* *
* - `templateUrl` `{string=|function()=}` path or function that returns a path to an html * One of `template` or `templateUrl` is required.
*
* - `templateUrl` `{(string|Function)=}` path or function that returns a path to an html
* template that should be used by {@link ngRoute.directive:ngView ngView}. * template that should be used by {@link ngRoute.directive:ngView ngView}.
* *
* If `templateUrl` is a function, it will be called with the following parameters: * If `templateUrl` is a function, it will be called with the following parameters:
@ -135,7 +147,9 @@ function $RouteProvider() {
* - `{Array.<Object>}` - route parameters extracted from the current * - `{Array.<Object>}` - route parameters extracted from the current
* `$location.path()` by applying the current route * `$location.path()` by applying the current route
* *
* - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should * One of `templateUrl` or `template` is required.
*
* - `resolve` - `{Object.<string, Function>=}` - An optional map of dependencies which should
* be injected into the controller. If any of these dependencies are promises, the router * be injected into the controller. If any of these dependencies are promises, the router
* will wait for them all to be resolved or one to be rejected before the controller is * will wait for them all to be resolved or one to be rejected before the controller is
* instantiated. * instantiated.
@ -155,7 +169,7 @@ function $RouteProvider() {
* The map object is: * The map object is:
* *
* - `key` `{string}`: a name of a dependency to be injected into the controller. * - `key` `{string}`: a name of a dependency to be injected into the controller.
* - `factory` - `{string|function}`: If `string` then it is an alias for a service. * - `factory` - `{string|Function}`: If `string` then it is an alias for a service.
* Otherwise if function, then it is {@link auto.$injector#invoke injected} * Otherwise if function, then it is {@link auto.$injector#invoke injected}
* and the return value is treated as the dependency. If the result is a promise, it is * and the return value is treated as the dependency. If the result is a promise, it is
* resolved before its value is injected into the controller. Be aware that * resolved before its value is injected into the controller. Be aware that
@ -165,7 +179,7 @@ function $RouteProvider() {
* - `resolveAs` - `{string=}` - The name under which the `resolve` map will be available on * - `resolveAs` - `{string=}` - The name under which the `resolve` map will be available on
* the scope of the route. If omitted, defaults to `$resolve`. * the scope of the route. If omitted, defaults to `$resolve`.
* *
* - `redirectTo` `{(string|function())=}` value to update * - `redirectTo` `{(string|Function)=}` value to update
* {@link ng.$location $location} path with and trigger route redirection. * {@link ng.$location $location} path with and trigger route redirection.
* *
* If `redirectTo` is a function, it will be called with the following parameters: * If `redirectTo` is a function, it will be called with the following parameters:
@ -176,13 +190,48 @@ function $RouteProvider() {
* - `{Object}` - current `$location.search()` * - `{Object}` - current `$location.search()`
* *
* The custom `redirectTo` function is expected to return a string which will be used * The custom `redirectTo` function is expected to return a string which will be used
* to update `$location.path()` and `$location.search()`. * to update `$location.url()`. If the function throws an error, no further processing will
* take place and the {@link ngRoute.$route#$routeChangeError $routeChangeError} event will
* be fired.
*
* Routes that specify `redirectTo` will not have their controllers, template functions
* or resolves called, the `$location` will be changed to the redirect url and route
* processing will stop. The exception to this is if the `redirectTo` is a function that
* returns `undefined`. In this case the route transition occurs as though there was no
* redirection.
*
* - `resolveRedirectTo` `{Function=}` a function that will (eventually) return the value
* to update {@link ng.$location $location} URL with and trigger route redirection. In
* contrast to `redirectTo`, dependencies can be injected into `resolveRedirectTo` and the
* return value can be either a string or a promise that will be resolved to a string.
*
* Similar to `redirectTo`, if the return value is `undefined` (or a promise that gets
* resolved to `undefined`), no redirection takes place and the route transition occurs as
* though there was no redirection.
*
* If the function throws an error or the returned promise gets rejected, no further
* processing will take place and the
* {@link ngRoute.$route#$routeChangeError $routeChangeError} event will be fired.
*
* `redirectTo` takes precedence over `resolveRedirectTo`, so specifying both on the same
* route definition, will cause the latter to be ignored.
*
* - `[reloadOnUrl=true]` - `{boolean=}` - reload route when any part of the URL changes
* (including the path) even if the new URL maps to the same route.
*
* If the option is set to `false` and the URL in the browser changes, but the new URL maps
* to the same route, then a `$routeUpdate` event is broadcasted on the root scope (without
* reloading the route).
* *
* - `[reloadOnSearch=true]` - `{boolean=}` - reload route when only `$location.search()` * - `[reloadOnSearch=true]` - `{boolean=}` - reload route when only `$location.search()`
* or `$location.hash()` changes. * or `$location.hash()` changes.
* *
* If the option is set to `false` and url in the browser changes, then * If the option is set to `false` and the URL in the browser changes, then a `$routeUpdate`
* `$routeUpdate` event is broadcasted on the root scope. * event is broadcasted on the root scope (without reloading the route).
*
* <div class="alert alert-warning">
* **Note:** This option has no effect if `reloadOnUrl` is set to `false`.
* </div>
* *
* - `[caseInsensitiveMatch=false]` - `{boolean=}` - match routes without being case sensitive * - `[caseInsensitiveMatch=false]` - `{boolean=}` - match routes without being case sensitive
* *
@ -197,6 +246,9 @@ function $RouteProvider() {
this.when = function(path, route) { this.when = function(path, route) {
//copy original route object to preserve params inherited from proto chain //copy original route object to preserve params inherited from proto chain
var routeCopy = shallowCopy(route); var routeCopy = shallowCopy(route);
if (angular.isUndefined(routeCopy.reloadOnUrl)) {
routeCopy.reloadOnUrl = true;
}
if (angular.isUndefined(routeCopy.reloadOnSearch)) { if (angular.isUndefined(routeCopy.reloadOnSearch)) {
routeCopy.reloadOnSearch = true; routeCopy.reloadOnSearch = true;
} }
@ -205,18 +257,19 @@ function $RouteProvider() {
} }
routes[path] = angular.extend( routes[path] = angular.extend(
routeCopy, routeCopy,
path && pathRegExp(path, routeCopy) {originalPath: path},
path && routeToRegExp(path, routeCopy)
); );
// create redirection for trailing slashes // create redirection for trailing slashes
if (path) { if (path) {
var redirectPath = (path[path.length - 1] == '/') var redirectPath = (path[path.length - 1] === '/')
? path.substr(0, path.length - 1) ? path.substr(0, path.length - 1)
: path + '/'; : path + '/';
routes[redirectPath] = angular.extend( routes[redirectPath] = angular.extend(
{redirectTo: path}, {originalPath: path, redirectTo: path},
pathRegExp(redirectPath, routeCopy) routeToRegExp(redirectPath, routeCopy)
); );
} }
@ -234,47 +287,6 @@ function $RouteProvider() {
*/ */
this.caseInsensitiveMatch = false; this.caseInsensitiveMatch = false;
/**
* @param path {string} path
* @param opts {Object} options
* @return {?Object}
*
* @description
* Normalizes the given path, returning a regular expression
* and the original path.
*
* Inspired by pathRexp in visionmedia/express/lib/utils.js.
*/
function pathRegExp(path, opts) {
var insensitive = opts.caseInsensitiveMatch,
ret = {
originalPath: path,
regexp: path
},
keys = ret.keys = [];
path = path
.replace(/([().])/g, '\\$1')
.replace(/(\/)?:(\w+)(\*\?|[\?\*])?/g, function(_, slash, key, option) {
var optional = (option === '?' || option === '*?') ? '?' : null;
var star = (option === '*' || option === '*?') ? '*' : null;
keys.push({ name: key, optional: !!optional });
slash = slash || '';
return ''
+ (optional ? '' : slash)
+ '(?:'
+ (optional ? slash : '')
+ (star && '(.+?)' || '([^/]+)')
+ (optional || '')
+ ')'
+ (optional || '');
})
.replace(/([\/$\*])/g, '\\$1');
ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : '');
return ret;
}
/** /**
* @ngdoc method * @ngdoc method
* @name $routeProvider#otherwise * @name $routeProvider#otherwise
@ -295,6 +307,47 @@ function $RouteProvider() {
return this; return this;
}; };
/**
* @ngdoc method
* @name $routeProvider#eagerInstantiationEnabled
* @kind function
*
* @description
* Call this method as a setter to enable/disable eager instantiation of the
* {@link ngRoute.$route $route} service upon application bootstrap. You can also call it as a
* getter (i.e. without any arguments) to get the current value of the
* `eagerInstantiationEnabled` flag.
*
* Instantiating `$route` early is necessary for capturing the initial
* {@link ng.$location#$locationChangeStart $locationChangeStart} event and navigating to the
* appropriate route. Usually, `$route` is instantiated in time by the
* {@link ngRoute.ngView ngView} directive. Yet, in cases where `ngView` is included in an
* asynchronously loaded template (e.g. in another directive's template), the directive factory
* might not be called soon enough for `$route` to be instantiated _before_ the initial
* `$locationChangeSuccess` event is fired. Eager instantiation ensures that `$route` is always
* instantiated in time, regardless of when `ngView` will be loaded.
*
* The default value is true.
*
* **Note**:<br />
* You may want to disable the default behavior when unit-testing modules that depend on
* `ngRoute`, in order to avoid an unexpected request for the default route's template.
*
* @param {boolean=} enabled - If provided, update the internal `eagerInstantiationEnabled` flag.
*
* @returns {*} The current value of the `eagerInstantiationEnabled` flag if used as a getter or
* itself (for chaining) if used as a setter.
*/
isEagerInstantiationEnabled = true;
this.eagerInstantiationEnabled = function eagerInstantiationEnabled(enabled) {
if (isDefined(enabled)) {
isEagerInstantiationEnabled = enabled;
return this;
}
return isEagerInstantiationEnabled;
};
this.$get = ['$rootScope', this.$get = ['$rootScope',
'$location', '$location',
@ -303,7 +356,8 @@ function $RouteProvider() {
'$injector', '$injector',
'$templateRequest', '$templateRequest',
'$sce', '$sce',
function($rootScope, $location, $routeParams, $q, $injector, $templateRequest, $sce) { '$browser',
function($rootScope, $location, $routeParams, $q, $injector, $templateRequest, $sce, $browser) {
/** /**
* @ngdoc service * @ngdoc service
@ -388,12 +442,12 @@ function $RouteProvider() {
* }) * })
* *
* .controller('BookController', function($scope, $routeParams) { * .controller('BookController', function($scope, $routeParams) {
* $scope.name = "BookController"; * $scope.name = 'BookController';
* $scope.params = $routeParams; * $scope.params = $routeParams;
* }) * })
* *
* .controller('ChapterController', function($scope, $routeParams) { * .controller('ChapterController', function($scope, $routeParams) {
* $scope.name = "ChapterController"; * $scope.name = 'ChapterController';
* $scope.params = $routeParams; * $scope.params = $routeParams;
* }) * })
* *
@ -426,15 +480,15 @@ function $RouteProvider() {
* it('should load and compile correct template', function() { * it('should load and compile correct template', function() {
* element(by.linkText('Moby: Ch1')).click(); * element(by.linkText('Moby: Ch1')).click();
* var content = element(by.css('[ng-view]')).getText(); * var content = element(by.css('[ng-view]')).getText();
* expect(content).toMatch(/controller\: ChapterController/); * expect(content).toMatch(/controller: ChapterController/);
* expect(content).toMatch(/Book Id\: Moby/); * expect(content).toMatch(/Book Id: Moby/);
* expect(content).toMatch(/Chapter Id\: 1/); * expect(content).toMatch(/Chapter Id: 1/);
* *
* element(by.partialLinkText('Scarlet')).click(); * element(by.partialLinkText('Scarlet')).click();
* *
* content = element(by.css('[ng-view]')).getText(); * content = element(by.css('[ng-view]')).getText();
* expect(content).toMatch(/controller\: BookController/); * expect(content).toMatch(/controller: BookController/);
* expect(content).toMatch(/Book Id\: Scarlet/); * expect(content).toMatch(/Book Id: Scarlet/);
* }); * });
* </file> * </file>
* </example> * </example>
@ -482,12 +536,14 @@ function $RouteProvider() {
* @name $route#$routeChangeError * @name $route#$routeChangeError
* @eventType broadcast on root scope * @eventType broadcast on root scope
* @description * @description
* Broadcasted if any of the resolve promises are rejected. * Broadcasted if a redirection function fails or any redirection or resolve promises are
* rejected.
* *
* @param {Object} angularEvent Synthetic event object * @param {Object} angularEvent Synthetic event object
* @param {Route} current Current route information. * @param {Route} current Current route information.
* @param {Route} previous Previous route information. * @param {Route} previous Previous route information.
* @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. * @param {Route} rejection The thrown error or the rejection reason of the promise. Usually
* the rejection reason is the error that caused the promise to get rejected.
*/ */
/** /**
@ -495,8 +551,9 @@ function $RouteProvider() {
* @name $route#$routeUpdate * @name $route#$routeUpdate
* @eventType broadcast on root scope * @eventType broadcast on root scope
* @description * @description
* The `reloadOnSearch` property has been set to false, and we are reusing the same * Broadcasted if the same instance of a route (including template, controller instance,
* instance of the Controller. * resolved dependencies, etc.) is being reused. This can happen if either `reloadOnSearch` or
* `reloadOnUrl` has been set to `false`.
* *
* @param {Object} angularEvent Synthetic event object * @param {Object} angularEvent Synthetic event object
* @param {Route} current Current/previous route information. * @param {Route} current Current/previous route information.
@ -556,7 +613,7 @@ function $RouteProvider() {
// interpolate modifies newParams, only query params are left // interpolate modifies newParams, only query params are left
$location.search(newParams); $location.search(newParams);
} else { } else {
throw $routeMinErr('norout', 'Tried updating route when with no current route'); throw $routeMinErr('norout', 'Tried updating route with no current route');
} }
} }
}; };
@ -604,9 +661,7 @@ function $RouteProvider() {
var lastRoute = $route.current; var lastRoute = $route.current;
preparedRoute = parseRoute(); preparedRoute = parseRoute();
preparedRouteIsUpdateOnly = preparedRoute && lastRoute && preparedRoute.$$route === lastRoute.$$route preparedRouteIsUpdateOnly = isNavigationUpdateOnly(preparedRoute, lastRoute);
&& angular.equals(preparedRoute.pathParams, lastRoute.pathParams)
&& !preparedRoute.reloadOnSearch && !forceReload;
if (!preparedRouteIsUpdateOnly && (lastRoute || preparedRoute)) { if (!preparedRouteIsUpdateOnly && (lastRoute || preparedRoute)) {
if ($rootScope.$broadcast('$routeChangeStart', preparedRoute, lastRoute).defaultPrevented) { if ($rootScope.$broadcast('$routeChangeStart', preparedRoute, lastRoute).defaultPrevented) {
@ -628,37 +683,112 @@ function $RouteProvider() {
} else if (nextRoute || lastRoute) { } else if (nextRoute || lastRoute) {
forceReload = false; forceReload = false;
$route.current = nextRoute; $route.current = nextRoute;
if (nextRoute) {
if (nextRoute.redirectTo) {
if (angular.isString(nextRoute.redirectTo)) {
$location.path(interpolate(nextRoute.redirectTo, nextRoute.params)).search(nextRoute.params)
.replace();
} else {
$location.url(nextRoute.redirectTo(nextRoute.pathParams, $location.path(), $location.search()))
.replace();
}
}
}
$q.when(nextRoute). var nextRoutePromise = $q.resolve(nextRoute);
then(resolveLocals).
then(function(locals) { $browser.$$incOutstandingRequestCount('$route');
// after route change
if (nextRoute == $route.current) { nextRoutePromise.
if (nextRoute) { then(getRedirectionData).
nextRoute.locals = locals; then(handlePossibleRedirection).
angular.copy(nextRoute.params, $routeParams); then(function(keepProcessingRoute) {
} return keepProcessingRoute && nextRoutePromise.
$rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute); then(resolveLocals).
} then(function(locals) {
}, function(error) { // after route change
if (nextRoute == $route.current) { if (nextRoute === $route.current) {
if (nextRoute) {
nextRoute.locals = locals;
angular.copy(nextRoute.params, $routeParams);
}
$rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute);
}
});
}).catch(function(error) {
if (nextRoute === $route.current) {
$rootScope.$broadcast('$routeChangeError', nextRoute, lastRoute, error); $rootScope.$broadcast('$routeChangeError', nextRoute, lastRoute, error);
} }
}).finally(function() {
// Because `commitRoute()` is called from a `$rootScope.$evalAsync` block (see
// `$locationWatch`), this `$$completeOutstandingRequest()` call will not cause
// `outstandingRequestCount` to hit zero. This is important in case we are redirecting
// to a new route which also requires some asynchronous work.
$browser.$$completeOutstandingRequest(noop, '$route');
}); });
} }
} }
function getRedirectionData(route) {
var data = {
route: route,
hasRedirection: false
};
if (route) {
if (route.redirectTo) {
if (angular.isString(route.redirectTo)) {
data.path = interpolate(route.redirectTo, route.params);
data.search = route.params;
data.hasRedirection = true;
} else {
var oldPath = $location.path();
var oldSearch = $location.search();
var newUrl = route.redirectTo(route.pathParams, oldPath, oldSearch);
if (angular.isDefined(newUrl)) {
data.url = newUrl;
data.hasRedirection = true;
}
}
} else if (route.resolveRedirectTo) {
return $q.
resolve($injector.invoke(route.resolveRedirectTo)).
then(function(newUrl) {
if (angular.isDefined(newUrl)) {
data.url = newUrl;
data.hasRedirection = true;
}
return data;
});
}
}
return data;
}
function handlePossibleRedirection(data) {
var keepProcessingRoute = true;
if (data.route !== $route.current) {
keepProcessingRoute = false;
} else if (data.hasRedirection) {
var oldUrl = $location.url();
var newUrl = data.url;
if (newUrl) {
$location.
url(newUrl).
replace();
} else {
newUrl = $location.
path(data.path).
search(data.search).
replace().
url();
}
if (newUrl !== oldUrl) {
// Exit out and don't process current next value,
// wait for next location change from redirect
keepProcessingRoute = false;
}
}
return keepProcessingRoute;
}
function resolveLocals(route) { function resolveLocals(route) {
if (route) { if (route) {
var locals = angular.extend({}, route.resolve); var locals = angular.extend({}, route.resolve);
@ -675,7 +805,6 @@ function $RouteProvider() {
} }
} }
function getTemplateFor(route) { function getTemplateFor(route) {
var template, templateUrl; var template, templateUrl;
if (angular.isDefined(template = route.template)) { if (angular.isDefined(template = route.template)) {
@ -694,7 +823,6 @@ function $RouteProvider() {
return template; return template;
} }
/** /**
* @returns {Object} the current active route, by matching it against the URL * @returns {Object} the current active route, by matching it against the URL
*/ */
@ -713,6 +841,29 @@ function $RouteProvider() {
return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
} }
/**
* @param {Object} newRoute - The new route configuration (as returned by `parseRoute()`).
* @param {Object} oldRoute - The previous route configuration (as returned by `parseRoute()`).
* @returns {boolean} Whether this is an "update-only" navigation, i.e. the URL maps to the same
* route and it can be reused (based on the config and the type of change).
*/
function isNavigationUpdateOnly(newRoute, oldRoute) {
// IF this is not a forced reload
return !forceReload
// AND both `newRoute`/`oldRoute` are defined
&& newRoute && oldRoute
// AND they map to the same Route Definition Object
&& (newRoute.$$route === oldRoute.$$route)
// AND `reloadOnUrl` is disabled
&& (!newRoute.reloadOnUrl
// OR `reloadOnSearch` is disabled
|| (!newRoute.reloadOnSearch
// AND both routes have the same path params
&& angular.equals(newRoute.pathParams, oldRoute.pathParams)
)
);
}
/** /**
* @returns {string} interpolation of the redirect path with the parameters * @returns {string} interpolation of the redirect path with the parameters
*/ */
@ -734,6 +885,14 @@ function $RouteProvider() {
}]; }];
} }
instantiateRoute.$inject = ['$injector'];
function instantiateRoute($injector) {
if (isEagerInstantiationEnabled) {
// Instantiate `$route`
$injector.get('$route');
}
}
ngRouteModule.provider('$routeParams', $RouteParamsProvider); ngRouteModule.provider('$routeParams', $RouteParamsProvider);
@ -741,6 +900,7 @@ ngRouteModule.provider('$routeParams', $RouteParamsProvider);
* @ngdoc service * @ngdoc service
* @name $routeParams * @name $routeParams
* @requires $route * @requires $route
* @this
* *
* @description * @description
* The `$routeParams` service allows you to retrieve the current set of route parameters. * The `$routeParams` service allows you to retrieve the current set of route parameters.
@ -784,7 +944,6 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
* @restrict ECA * @restrict ECA
* *
* @description * @description
* # Overview
* `ngView` is a directive that complements the {@link ngRoute.$route $route} service by * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by
* including the rendered template of the current route into the main layout (`index.html`) file. * including the rendered template of the current route into the main layout (`index.html`) file.
* Every time the current route changes, the included view changes with it according to the * Every time the current route changes, the included view changes with it according to the
@ -800,13 +959,6 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
* *
* The enter and leave animation occur concurrently. * The enter and leave animation occur concurrently.
* *
* @knownIssue If `ngView` is contained in an asynchronously loaded template (e.g. in another
* directive's templateUrl or in a template loaded using `ngInclude`), then you need to
* make sure that `$route` is instantiated in time to capture the initial
* `$locationChangeStart` event and load the appropriate view. One way to achieve this
* is to have it as a dependency in a `.run` block:
* `myModule.run(['$route', function() {}]);`
*
* @scope * @scope
* @priority 400 * @priority 400
* @param {string=} onload Expression to evaluate whenever the view updates. * @param {string=} onload Expression to evaluate whenever the view updates.
@ -917,17 +1069,17 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
$locationProvider.html5Mode(true); $locationProvider.html5Mode(true);
}]) }])
.controller('MainCtrl', ['$route', '$routeParams', '$location', .controller('MainCtrl', ['$route', '$routeParams', '$location',
function($route, $routeParams, $location) { function MainCtrl($route, $routeParams, $location) {
this.$route = $route; this.$route = $route;
this.$location = $location; this.$location = $location;
this.$routeParams = $routeParams; this.$routeParams = $routeParams;
}]) }])
.controller('BookCtrl', ['$routeParams', function($routeParams) { .controller('BookCtrl', ['$routeParams', function BookCtrl($routeParams) {
this.name = "BookCtrl"; this.name = 'BookCtrl';
this.params = $routeParams; this.params = $routeParams;
}]) }])
.controller('ChapterCtrl', ['$routeParams', function($routeParams) { .controller('ChapterCtrl', ['$routeParams', function ChapterCtrl($routeParams) {
this.name = "ChapterCtrl"; this.name = 'ChapterCtrl';
this.params = $routeParams; this.params = $routeParams;
}]); }]);
@ -937,15 +1089,15 @@ ngRouteModule.directive('ngView', ngViewFillContentFactory);
it('should load and compile correct template', function() { it('should load and compile correct template', function() {
element(by.linkText('Moby: Ch1')).click(); element(by.linkText('Moby: Ch1')).click();
var content = element(by.css('[ng-view]')).getText(); var content = element(by.css('[ng-view]')).getText();
expect(content).toMatch(/controller\: ChapterCtrl/); expect(content).toMatch(/controller: ChapterCtrl/);
expect(content).toMatch(/Book Id\: Moby/); expect(content).toMatch(/Book Id: Moby/);
expect(content).toMatch(/Chapter Id\: 1/); expect(content).toMatch(/Chapter Id: 1/);
element(by.partialLinkText('Scarlet')).click(); element(by.partialLinkText('Scarlet')).click();
content = element(by.css('[ng-view]')).getText(); content = element(by.css('[ng-view]')).getText();
expect(content).toMatch(/controller\: BookCtrl/); expect(content).toMatch(/controller: BookCtrl/);
expect(content).toMatch(/Book Id\: Scarlet/); expect(content).toMatch(/Book Id: Scarlet/);
}); });
</file> </file>
</example> </example>
@ -988,8 +1140,8 @@ function ngViewFactory($route, $anchorScroll, $animate) {
} }
if (currentElement) { if (currentElement) {
previousLeaveAnimation = $animate.leave(currentElement); previousLeaveAnimation = $animate.leave(currentElement);
previousLeaveAnimation.then(function() { previousLeaveAnimation.done(function(response) {
previousLeaveAnimation = null; if (response !== false) previousLeaveAnimation = null;
}); });
currentElement = null; currentElement = null;
} }
@ -1010,8 +1162,8 @@ function ngViewFactory($route, $anchorScroll, $animate) {
// function is called before linking the content, which would apply child // function is called before linking the content, which would apply child
// directives to non existing elements. // directives to non existing elements.
var clone = $transclude(newScope, function(clone) { var clone = $transclude(newScope, function(clone) {
$animate.enter(clone, null, currentElement || $element).then(function onNgViewEnter() { $animate.enter(clone, null, currentElement || $element).done(function onNgViewEnter(response) {
if (angular.isDefined(autoScrollExp) if (response !== false && angular.isDefined(autoScrollExp)
&& (!autoScrollExp || scope.$eval(autoScrollExp))) { && (!autoScrollExp || scope.$eval(autoScrollExp))) {
$anchorScroll(); $anchorScroll();
} }

View File

@ -1,6 +1,6 @@
/** /**
* @license AngularJS v1.5.8 * @license AngularJS v1.8.2
* (c) 2010-2016 Google, Inc. http://angularjs.org * (c) 2010-2020 Google, Inc. http://angularjs.org
* License: MIT * License: MIT
*/ */
(function(window, angular) {'use strict'; (function(window, angular) {'use strict';
@ -20,9 +20,11 @@ var $sanitizeMinErr = angular.$$minErr('$sanitize');
var bind; var bind;
var extend; var extend;
var forEach; var forEach;
var isArray;
var isDefined; var isDefined;
var lowercase; var lowercase;
var noop; var noop;
var nodeContains;
var htmlParser; var htmlParser;
var htmlSanitizeWriter; var htmlSanitizeWriter;
@ -31,13 +33,8 @@ var htmlSanitizeWriter;
* @name ngSanitize * @name ngSanitize
* @description * @description
* *
* # ngSanitize
*
* The `ngSanitize` module provides functionality to sanitize HTML. * The `ngSanitize` module provides functionality to sanitize HTML.
* *
*
* <div doc-module-components="ngSanitize"></div>
*
* See {@link ngSanitize.$sanitize `$sanitize`} for usage. * See {@link ngSanitize.$sanitize `$sanitize`} for usage.
*/ */
@ -49,13 +46,12 @@ var htmlSanitizeWriter;
* @description * @description
* Sanitizes an html string by stripping all potentially dangerous tokens. * Sanitizes an html string by stripping all potentially dangerous tokens.
* *
* The input is sanitized by parsing the HTML into tokens. All safe tokens (from a whitelist) are * The input is sanitized by parsing the HTML into tokens. All safe tokens (from a trusted URI list) are
* then serialized back to properly escaped html string. This means that no unsafe input can make * then serialized back to a properly escaped HTML string. This means that no unsafe input can make
* it into the returned string. * it into the returned string.
* *
* The whitelist for URL sanitization of attribute values is configured using the functions * The trusted URIs for URL sanitization of attribute values is configured using the functions
* `aHrefSanitizationWhitelist` and `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider * `aHrefSanitizationTrustedUrlList` and `imgSrcSanitizationTrustedUrlList` of {@link $compileProvider}.
* `$compileProvider`}.
* *
* The input may also contain SVG markup if this is enabled via {@link $sanitizeProvider}. * The input may also contain SVG markup if this is enabled via {@link $sanitizeProvider}.
* *
@ -63,7 +59,7 @@ var htmlSanitizeWriter;
* @returns {string} Sanitized HTML. * @returns {string} Sanitized HTML.
* *
* @example * @example
<example module="sanitizeExample" deps="angular-sanitize.js"> <example module="sanitizeExample" deps="angular-sanitize.js" name="sanitize-service">
<file name="index.html"> <file name="index.html">
<script> <script>
angular.module('sanitizeExample', ['ngSanitize']) angular.module('sanitizeExample', ['ngSanitize'])
@ -112,19 +108,19 @@ var htmlSanitizeWriter;
</file> </file>
<file name="protractor.js" type="protractor"> <file name="protractor.js" type="protractor">
it('should sanitize the html snippet by default', function() { it('should sanitize the html snippet by default', function() {
expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')).
toBe('<p>an html\n<em>click here</em>\nsnippet</p>'); toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
}); });
it('should inline raw snippet if bound to a trusted value', function() { it('should inline raw snippet if bound to a trusted value', function() {
expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()). expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')).
toBe("<p style=\"color:blue\">an html\n" + toBe("<p style=\"color:blue\">an html\n" +
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
"snippet</p>"); "snippet</p>");
}); });
it('should escape snippet without any filter', function() { it('should escape snippet without any filter', function() {
expect(element(by.css('#bind-default div')).getInnerHtml()). expect(element(by.css('#bind-default div')).getAttribute('innerHTML')).
toBe("&lt;p style=\"color:blue\"&gt;an html\n" + toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
"&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" + "&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
"snippet&lt;/p&gt;"); "snippet&lt;/p&gt;");
@ -133,11 +129,11 @@ var htmlSanitizeWriter;
it('should update', function() { it('should update', function() {
element(by.model('snippet')).clear(); element(by.model('snippet')).clear();
element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>'); element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>');
expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()). expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')).
toBe('new <b>text</b>'); toBe('new <b>text</b>');
expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe( expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')).toBe(
'new <b onclick="alert(1)">text</b>'); 'new <b onclick="alert(1)">text</b>');
expect(element(by.css('#bind-default div')).getInnerHtml()).toBe( expect(element(by.css('#bind-default div')).getAttribute('innerHTML')).toBe(
"new &lt;b onclick=\"alert(1)\"&gt;text&lt;/b&gt;"); "new &lt;b onclick=\"alert(1)\"&gt;text&lt;/b&gt;");
}); });
</file> </file>
@ -148,14 +144,17 @@ var htmlSanitizeWriter;
/** /**
* @ngdoc provider * @ngdoc provider
* @name $sanitizeProvider * @name $sanitizeProvider
* @this
* *
* @description * @description
* Creates and configures {@link $sanitize} instance. * Creates and configures {@link $sanitize} instance.
*/ */
function $SanitizeProvider() { function $SanitizeProvider() {
var hasBeenInstantiated = false;
var svgEnabled = false; var svgEnabled = false;
this.$get = ['$$sanitizeUri', function($$sanitizeUri) { this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
hasBeenInstantiated = true;
if (svgEnabled) { if (svgEnabled) {
extend(validElements, svgElements); extend(validElements, svgElements);
} }
@ -196,7 +195,7 @@ function $SanitizeProvider() {
* </div> * </div>
* *
* @param {boolean=} flag Enable or disable SVG support in the sanitizer. * @param {boolean=} flag Enable or disable SVG support in the sanitizer.
* @returns {boolean|ng.$sanitizeProvider} Returns the currently configured value if called * @returns {boolean|$sanitizeProvider} Returns the currently configured value if called
* without an argument or self for chaining otherwise. * without an argument or self for chaining otherwise.
*/ */
this.enableSvg = function(enableSvg) { this.enableSvg = function(enableSvg) {
@ -208,6 +207,105 @@ function $SanitizeProvider() {
} }
}; };
/**
* @ngdoc method
* @name $sanitizeProvider#addValidElements
* @kind function
*
* @description
* Extends the built-in lists of valid HTML/SVG elements, i.e. elements that are considered safe
* and are not stripped off during sanitization. You can extend the following lists of elements:
*
* - `htmlElements`: A list of elements (tag names) to extend the current list of safe HTML
* elements. HTML elements considered safe will not be removed during sanitization. All other
* elements will be stripped off.
*
* - `htmlVoidElements`: This is similar to `htmlElements`, but marks the elements as
* "void elements" (similar to HTML
* [void elements](https://rawgit.com/w3c/html/html5.1-2/single-page.html#void-elements)). These
* elements have no end tag and cannot have content.
*
* - `svgElements`: This is similar to `htmlElements`, but for SVG elements. This list is only
* taken into account if SVG is {@link ngSanitize.$sanitizeProvider#enableSvg enabled} for
* `$sanitize`.
*
* <div class="alert alert-info">
* This method must be called during the {@link angular.Module#config config} phase. Once the
* `$sanitize` service has been instantiated, this method has no effect.
* </div>
*
* <div class="alert alert-warning">
* Keep in mind that extending the built-in lists of elements may expose your app to XSS or
* other vulnerabilities. Be very mindful of the elements you add.
* </div>
*
* @param {Array<String>|Object} elements - A list of valid HTML elements or an object with one or
* more of the following properties:
* - **htmlElements** - `{Array<String>}` - A list of elements to extend the current list of
* HTML elements.
* - **htmlVoidElements** - `{Array<String>}` - A list of elements to extend the current list of
* void HTML elements; i.e. elements that do not have an end tag.
* - **svgElements** - `{Array<String>}` - A list of elements to extend the current list of SVG
* elements. The list of SVG elements is only taken into account if SVG is
* {@link ngSanitize.$sanitizeProvider#enableSvg enabled} for `$sanitize`.
*
* Passing an array (`[...]`) is equivalent to passing `{htmlElements: [...]}`.
*
* @return {$sanitizeProvider} Returns self for chaining.
*/
this.addValidElements = function(elements) {
if (!hasBeenInstantiated) {
if (isArray(elements)) {
elements = {htmlElements: elements};
}
addElementsTo(svgElements, elements.svgElements);
addElementsTo(voidElements, elements.htmlVoidElements);
addElementsTo(validElements, elements.htmlVoidElements);
addElementsTo(validElements, elements.htmlElements);
}
return this;
};
/**
* @ngdoc method
* @name $sanitizeProvider#addValidAttrs
* @kind function
*
* @description
* Extends the built-in list of valid attributes, i.e. attributes that are considered safe and are
* not stripped off during sanitization.
*
* **Note**:
* The new attributes will not be treated as URI attributes, which means their values will not be
* sanitized as URIs using `$compileProvider`'s
* {@link ng.$compileProvider#aHrefSanitizationTrustedUrlList aHrefSanitizationTrustedUrlList} and
* {@link ng.$compileProvider#imgSrcSanitizationTrustedUrlList imgSrcSanitizationTrustedUrlList}.
*
* <div class="alert alert-info">
* This method must be called during the {@link angular.Module#config config} phase. Once the
* `$sanitize` service has been instantiated, this method has no effect.
* </div>
*
* <div class="alert alert-warning">
* Keep in mind that extending the built-in list of attributes may expose your app to XSS or
* other vulnerabilities. Be very mindful of the attributes you add.
* </div>
*
* @param {Array<String>} attrs - A list of valid attributes.
*
* @returns {$sanitizeProvider} Returns self for chaining.
*/
this.addValidAttrs = function(attrs) {
if (!hasBeenInstantiated) {
extend(validAttrs, arrayToMap(attrs, true));
}
return this;
};
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
// Private stuff // Private stuff
////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////
@ -215,17 +313,23 @@ function $SanitizeProvider() {
bind = angular.bind; bind = angular.bind;
extend = angular.extend; extend = angular.extend;
forEach = angular.forEach; forEach = angular.forEach;
isArray = angular.isArray;
isDefined = angular.isDefined; isDefined = angular.isDefined;
lowercase = angular.lowercase; lowercase = angular.$$lowercase;
noop = angular.noop; noop = angular.noop;
htmlParser = htmlParserImpl; htmlParser = htmlParserImpl;
htmlSanitizeWriter = htmlSanitizeWriterImpl; htmlSanitizeWriter = htmlSanitizeWriterImpl;
nodeContains = window.Node.prototype.contains || /** @this */ function(arg) {
// eslint-disable-next-line no-bitwise
return !!(this.compareDocumentPosition(arg) & 16);
};
// Regular Expressions for parsing tags and attributes // Regular Expressions for parsing tags and attributes
var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
// Match everything outside of normal chars and " (quote character) // Match everything outside of normal chars and " (quote character)
NON_ALPHANUMERIC_REGEXP = /([^\#-~ |!])/g; NON_ALPHANUMERIC_REGEXP = /([^#-~ |!])/g;
// Good source of info about elements and attributes // Good source of info about elements and attributes
@ -234,36 +338,36 @@ function $SanitizeProvider() {
// Safe Void Elements - HTML5 // Safe Void Elements - HTML5
// http://dev.w3.org/html5/spec/Overview.html#void-elements // http://dev.w3.org/html5/spec/Overview.html#void-elements
var voidElements = toMap("area,br,col,hr,img,wbr"); var voidElements = stringToMap('area,br,col,hr,img,wbr');
// Elements that you can, intentionally, leave open (and which close themselves) // Elements that you can, intentionally, leave open (and which close themselves)
// http://dev.w3.org/html5/spec/Overview.html#optional-tags // http://dev.w3.org/html5/spec/Overview.html#optional-tags
var optionalEndTagBlockElements = toMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), var optionalEndTagBlockElements = stringToMap('colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr'),
optionalEndTagInlineElements = toMap("rp,rt"), optionalEndTagInlineElements = stringToMap('rp,rt'),
optionalEndTagElements = extend({}, optionalEndTagElements = extend({},
optionalEndTagInlineElements, optionalEndTagInlineElements,
optionalEndTagBlockElements); optionalEndTagBlockElements);
// Safe Block Elements - HTML5 // Safe Block Elements - HTML5
var blockElements = extend({}, optionalEndTagBlockElements, toMap("address,article," + var blockElements = extend({}, optionalEndTagBlockElements, stringToMap('address,article,' +
"aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," + 'aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,' +
"h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul")); 'h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,section,table,ul'));
// Inline Elements - HTML5 // Inline Elements - HTML5
var inlineElements = extend({}, optionalEndTagInlineElements, toMap("a,abbr,acronym,b," + var inlineElements = extend({}, optionalEndTagInlineElements, stringToMap('a,abbr,acronym,b,' +
"bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," + 'bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,' +
"samp,small,span,strike,strong,sub,sup,time,tt,u,var")); 'samp,small,span,strike,strong,sub,sup,time,tt,u,var'));
// SVG Elements // SVG Elements
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements
// Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted. // Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted.
// They can potentially allow for arbitrary javascript to be executed. See #11290 // They can potentially allow for arbitrary javascript to be executed. See #11290
var svgElements = toMap("circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph," + var svgElements = stringToMap('circle,defs,desc,ellipse,font-face,font-face-name,font-face-src,g,glyph,' +
"hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline," + 'hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,' +
"radialGradient,rect,stop,svg,switch,text,title,tspan"); 'radialGradient,rect,stop,svg,switch,text,title,tspan');
// Blocked Elements (will be stripped) // Blocked Elements (will be stripped)
var blockedElements = toMap("script,style"); var blockedElements = stringToMap('script,style');
var validElements = extend({}, var validElements = extend({},
voidElements, voidElements,
@ -272,9 +376,9 @@ function $SanitizeProvider() {
optionalEndTagElements); optionalEndTagElements);
//Attributes that have href and hence need to be sanitized //Attributes that have href and hence need to be sanitized
var uriAttrs = toMap("background,cite,href,longdesc,src,xlink:href"); var uriAttrs = stringToMap('background,cite,href,longdesc,src,xlink:href,xml:base');
var htmlAttrs = toMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' + var htmlAttrs = stringToMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' + 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' + 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' + 'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' +
@ -282,7 +386,7 @@ function $SanitizeProvider() {
// SVG attributes (without "id" and "name" attributes) // SVG attributes (without "id" and "name" attributes)
// https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes
var svgAttrs = toMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' + var svgAttrs = stringToMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' + 'baseProfile,bbox,begin,by,calcMode,cap-height,class,color,color-rendering,content,' +
'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' + 'cx,cy,d,dx,dy,descent,display,dur,end,fill,fill-rule,font-family,font-size,font-stretch,' +
'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' + 'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' +
@ -303,35 +407,74 @@ function $SanitizeProvider() {
svgAttrs, svgAttrs,
htmlAttrs); htmlAttrs);
function toMap(str, lowercaseKeys) { function stringToMap(str, lowercaseKeys) {
var obj = {}, items = str.split(','), i; return arrayToMap(str.split(','), lowercaseKeys);
}
function arrayToMap(items, lowercaseKeys) {
var obj = {}, i;
for (i = 0; i < items.length; i++) { for (i = 0; i < items.length; i++) {
obj[lowercaseKeys ? lowercase(items[i]) : items[i]] = true; obj[lowercaseKeys ? lowercase(items[i]) : items[i]] = true;
} }
return obj; return obj;
} }
var inertBodyElement; function addElementsTo(elementsMap, newElements) {
(function(window) { if (newElements && newElements.length) {
var doc; extend(elementsMap, arrayToMap(newElements));
if (window.document && window.document.implementation) {
doc = window.document.implementation.createHTMLDocument("inert");
} else {
throw $sanitizeMinErr('noinert', "Can't create an inert html document");
} }
var docElement = doc.documentElement || doc.getDocumentElement(); }
var bodyElements = docElement.getElementsByTagName('body');
// usually there should be only one body element in the document, but IE doesn't have any, so we need to create one /**
if (bodyElements.length === 1) { * Create an inert document that contains the dirty HTML that needs sanitizing.
inertBodyElement = bodyElements[0]; * We use the DOMParser API by default and fall back to createHTMLDocument if DOMParser is not
} else { * available.
var html = doc.createElement('html'); */
inertBodyElement = doc.createElement('body'); var getInertBodyElement /* function(html: string): HTMLBodyElement */ = (function(window, document) {
html.appendChild(inertBodyElement); if (isDOMParserAvailable()) {
doc.appendChild(html); return getInertBodyElement_DOMParser;
} }
})(window);
if (!document || !document.implementation) {
throw $sanitizeMinErr('noinert', 'Can\'t create an inert html document');
}
var inertDocument = document.implementation.createHTMLDocument('inert');
var inertBodyElement = (inertDocument.documentElement || inertDocument.getDocumentElement()).querySelector('body');
return getInertBodyElement_InertDocument;
function isDOMParserAvailable() {
try {
return !!getInertBodyElement_DOMParser('');
} catch (e) {
return false;
}
}
function getInertBodyElement_DOMParser(html) {
// We add this dummy element to ensure that the rest of the content is parsed as expected
// e.g. leading whitespace is maintained and tags like `<meta>` do not get hoisted to the `<head>` tag.
html = '<remove></remove>' + html;
try {
var body = new window.DOMParser().parseFromString(html, 'text/html').body;
body.firstChild.remove();
return body;
} catch (e) {
return undefined;
}
}
function getInertBodyElement_InertDocument(html) {
inertBodyElement.innerHTML = html;
// Support: IE 9-11 only
// strip custom-namespaced attributes on IE<=11
if (document.documentMode) {
stripCustomNsAttrs(inertBodyElement);
}
return inertBodyElement;
}
})(window, window.document);
/** /**
* @example * @example
@ -351,22 +494,21 @@ function $SanitizeProvider() {
} else if (typeof html !== 'string') { } else if (typeof html !== 'string') {
html = '' + html; html = '' + html;
} }
inertBodyElement.innerHTML = html;
var inertBodyElement = getInertBodyElement(html);
if (!inertBodyElement) return '';
//mXSS protection //mXSS protection
var mXSSAttempts = 5; var mXSSAttempts = 5;
do { do {
if (mXSSAttempts === 0) { if (mXSSAttempts === 0) {
throw $sanitizeMinErr('uinput', "Failed to sanitize html because the input is unstable"); throw $sanitizeMinErr('uinput', 'Failed to sanitize html because the input is unstable');
} }
mXSSAttempts--; mXSSAttempts--;
// strip custom-namespaced attributes on IE<=11 // trigger mXSS if it is going to happen by reading and writing the innerHTML
if (window.document.documentMode) { html = inertBodyElement.innerHTML;
stripCustomNsAttrs(inertBodyElement); inertBodyElement = getInertBodyElement(html);
}
html = inertBodyElement.innerHTML; //trigger mXSS
inertBodyElement.innerHTML = html;
} while (html !== inertBodyElement.innerHTML); } while (html !== inertBodyElement.innerHTML);
var node = inertBodyElement.firstChild; var node = inertBodyElement.firstChild;
@ -382,16 +524,16 @@ function $SanitizeProvider() {
var nextNode; var nextNode;
if (!(nextNode = node.firstChild)) { if (!(nextNode = node.firstChild)) {
if (node.nodeType == 1) { if (node.nodeType === 1) {
handler.end(node.nodeName.toLowerCase()); handler.end(node.nodeName.toLowerCase());
} }
nextNode = node.nextSibling; nextNode = getNonDescendant('nextSibling', node);
if (!nextNode) { if (!nextNode) {
while (nextNode == null) { while (nextNode == null) {
node = node.parentNode; node = getNonDescendant('parentNode', node);
if (node === inertBodyElement) break; if (node === inertBodyElement) break;
nextNode = node.nextSibling; nextNode = getNonDescendant('nextSibling', node);
if (node.nodeType == 1) { if (node.nodeType === 1) {
handler.end(node.nodeName.toLowerCase()); handler.end(node.nodeName.toLowerCase());
} }
} }
@ -400,7 +542,7 @@ function $SanitizeProvider() {
node = nextNode; node = nextNode;
} }
while (node = inertBodyElement.firstChild) { while ((node = inertBodyElement.firstChild)) {
inertBodyElement.removeChild(node); inertBodyElement.removeChild(node);
} }
} }
@ -481,6 +623,7 @@ function $SanitizeProvider() {
out(tag); out(tag);
out('>'); out('>');
} }
// eslint-disable-next-line eqeqeq
if (tag == ignoreCurrentElement) { if (tag == ignoreCurrentElement) {
ignoreCurrentElement = false; ignoreCurrentElement = false;
} }
@ -502,29 +645,37 @@ function $SanitizeProvider() {
* @param node Root element to process * @param node Root element to process
*/ */
function stripCustomNsAttrs(node) { function stripCustomNsAttrs(node) {
if (node.nodeType === window.Node.ELEMENT_NODE) { while (node) {
var attrs = node.attributes; if (node.nodeType === window.Node.ELEMENT_NODE) {
for (var i = 0, l = attrs.length; i < l; i++) { var attrs = node.attributes;
var attrNode = attrs[i]; for (var i = 0, l = attrs.length; i < l; i++) {
var attrName = attrNode.name.toLowerCase(); var attrNode = attrs[i];
if (attrName === 'xmlns:ns1' || attrName.lastIndexOf('ns1:', 0) === 0) { var attrName = attrNode.name.toLowerCase();
node.removeAttributeNode(attrNode); if (attrName === 'xmlns:ns1' || attrName.lastIndexOf('ns1:', 0) === 0) {
i--; node.removeAttributeNode(attrNode);
l--; i--;
l--;
}
} }
} }
}
var nextNode = node.firstChild; var nextNode = node.firstChild;
if (nextNode) { if (nextNode) {
stripCustomNsAttrs(nextNode); stripCustomNsAttrs(nextNode);
} }
nextNode = node.nextSibling; node = getNonDescendant('nextSibling', node);
if (nextNode) {
stripCustomNsAttrs(nextNode);
} }
} }
function getNonDescendant(propName, node) {
// An element is clobbered if its `propName` property points to one of its descendants
var nextNode = node[propName];
if (nextNode && nodeContains.call(node, nextNode)) {
throw $sanitizeMinErr('elclob', 'Failed to sanitize html because the element is clobbered: {0}', node.outerHTML || node.outerText);
}
return nextNode;
}
} }
function sanitizeText(chars) { function sanitizeText(chars) {
@ -536,7 +687,9 @@ function sanitizeText(chars) {
// define ngSanitize module and register $sanitize service // define ngSanitize module and register $sanitize service
angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider); angular.module('ngSanitize', [])
.provider('$sanitize', $SanitizeProvider)
.info({ angularVersion: '"1.8.2"' });
/** /**
* @ngdoc filter * @ngdoc filter
@ -544,13 +697,13 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
* @kind function * @kind function
* *
* @description * @description
* Finds links in text input and turns them into html links. Supports `http/https/ftp/mailto` and * Finds links in text input and turns them into html links. Supports `http/https/ftp/sftp/mailto` and
* plain email address links. * plain email address links.
* *
* Requires the {@link ngSanitize `ngSanitize`} module to be installed. * Requires the {@link ngSanitize `ngSanitize`} module to be installed.
* *
* @param {string} text Input text. * @param {string} text Input text.
* @param {string} target Window (`_blank|_self|_parent|_top`) or named frame to open links in. * @param {string} [target] Window (`_blank|_self|_parent|_top`) or named frame to open links in.
* @param {object|function(url)} [attributes] Add custom attributes to the link element. * @param {object|function(url)} [attributes] Add custom attributes to the link element.
* *
* Can be one of: * Can be one of:
@ -568,7 +721,7 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
<span ng-bind-html="linky_expression | linky"></span> <span ng-bind-html="linky_expression | linky"></span>
* *
* @example * @example
<example module="linkyExample" deps="angular-sanitize.js"> <example module="linkyExample" deps="angular-sanitize.js" name="linky-filter">
<file name="index.html"> <file name="index.html">
<div ng-controller="ExampleController"> <div ng-controller="ExampleController">
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea> Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
@ -616,10 +769,10 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
angular.module('linkyExample', ['ngSanitize']) angular.module('linkyExample', ['ngSanitize'])
.controller('ExampleController', ['$scope', function($scope) { .controller('ExampleController', ['$scope', function($scope) {
$scope.snippet = $scope.snippet =
'Pretty text with some links:\n'+ 'Pretty text with some links:\n' +
'http://angularjs.org/,\n'+ 'http://angularjs.org/,\n' +
'mailto:us@somewhere.org,\n'+ 'mailto:us@somewhere.org,\n' +
'another@somewhere.org,\n'+ 'another@somewhere.org,\n' +
'and one more: ftp://127.0.0.1/.'; 'and one more: ftp://127.0.0.1/.';
$scope.snippetWithSingleURL = 'http://angularjs.org/'; $scope.snippetWithSingleURL = 'http://angularjs.org/';
}]); }]);
@ -667,7 +820,7 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
*/ */
angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) { angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
var LINKY_URL_REGEXP = var LINKY_URL_REGEXP =
/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i, /((s?ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i,
MAILTO_REGEXP = /^mailto:/i; MAILTO_REGEXP = /^mailto:/i;
var linkyMinErr = angular.$$minErr('linky'); var linkyMinErr = angular.$$minErr('linky');

File diff suppressed because it is too large Load Diff

View File

@ -1,135 +1,38 @@
/** /**
* @license AngularJS v1.5.8 * @license AngularJS v1.8.2
* (c) 2010-2016 Google, Inc. http://angularjs.org * (c) 2010-2020 Google, Inc. http://angularjs.org
* License: MIT * License: MIT
*/ */
(function(window, angular) {'use strict'; (function(window, angular) {'use strict';
/* global ngTouchClickDirectiveFactory: false,
*/
/** /**
* @ngdoc module * @ngdoc module
* @name ngTouch * @name ngTouch
* @description * @description
* *
* # ngTouch * The `ngTouch` module provides helpers for touch-enabled devices.
*
* The `ngTouch` module provides touch events and other helpers for touch-enabled devices.
* The implementation is based on jQuery Mobile touch event handling * The implementation is based on jQuery Mobile touch event handling
* ([jquerymobile.com](http://jquerymobile.com/)). * ([jquerymobile.com](http://jquerymobile.com/)). *
*
* *
* See {@link ngTouch.$swipe `$swipe`} for usage. * See {@link ngTouch.$swipe `$swipe`} for usage.
* *
* <div doc-module-components="ngTouch"></div> * @deprecated
* * sinceVersion="1.7.0"
* The ngTouch module with the {@link ngTouch.$swipe `$swipe`} service and
* the {@link ngTouch.ngSwipeLeft} and {@link ngTouch.ngSwipeRight} directives are
* deprecated. Instead, stand-alone libraries for touch handling and gesture interaction
* should be used, for example [HammerJS](https://hammerjs.github.io/) (which is also used by
* Angular).
*/ */
// define ngTouch module // define ngTouch module
/* global -ngTouch */ /* global ngTouch */
var ngTouch = angular.module('ngTouch', []); var ngTouch = angular.module('ngTouch', []);
ngTouch.provider('$touch', $TouchProvider); ngTouch.info({ angularVersion: '"1.8.2"' });
function nodeName_(element) { function nodeName_(element) {
return angular.lowercase(element.nodeName || (element[0] && element[0].nodeName)); return angular.$$lowercase(element.nodeName || (element[0] && element[0].nodeName));
}
/**
* @ngdoc provider
* @name $touchProvider
*
* @description
* The `$touchProvider` allows enabling / disabling {@link ngTouch.ngClick ngTouch's ngClick directive}.
*/
$TouchProvider.$inject = ['$provide', '$compileProvider'];
function $TouchProvider($provide, $compileProvider) {
/**
* @ngdoc method
* @name $touchProvider#ngClickOverrideEnabled
*
* @param {boolean=} enabled update the ngClickOverrideEnabled state if provided, otherwise just return the
* current ngClickOverrideEnabled state
* @returns {*} current value if used as getter or itself (chaining) if used as setter
*
* @kind function
*
* @description
* Call this method to enable/disable {@link ngTouch.ngClick ngTouch's ngClick directive}. If enabled,
* the default ngClick directive will be replaced by a version that eliminates the 300ms delay for
* click events on browser for touch-devices.
*
* The default is `false`.
*
*/
var ngClickOverrideEnabled = false;
var ngClickDirectiveAdded = false;
this.ngClickOverrideEnabled = function(enabled) {
if (angular.isDefined(enabled)) {
if (enabled && !ngClickDirectiveAdded) {
ngClickDirectiveAdded = true;
// Use this to identify the correct directive in the delegate
ngTouchClickDirectiveFactory.$$moduleName = 'ngTouch';
$compileProvider.directive('ngClick', ngTouchClickDirectiveFactory);
$provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
if (ngClickOverrideEnabled) {
// drop the default ngClick directive
$delegate.shift();
} else {
// drop the ngTouch ngClick directive if the override has been re-disabled (because
// we cannot de-register added directives)
var i = $delegate.length - 1;
while (i >= 0) {
if ($delegate[i].$$moduleName === 'ngTouch') {
$delegate.splice(i, 1);
break;
}
i--;
}
}
return $delegate;
}]);
}
ngClickOverrideEnabled = enabled;
return this;
}
return ngClickOverrideEnabled;
};
/**
* @ngdoc service
* @name $touch
* @kind object
*
* @description
* Provides the {@link ngTouch.$touch#ngClickOverrideEnabled `ngClickOverrideEnabled`} method.
*
*/
this.$get = function() {
return {
/**
* @ngdoc method
* @name $touch#ngClickOverrideEnabled
*
* @returns {*} current value of `ngClickOverrideEnabled` set in the {@link ngTouch.$touchProvider $touchProvider},
* i.e. if {@link ngTouch.ngClick ngTouch's ngClick} directive is enabled.
*
* @kind function
*/
ngClickOverrideEnabled: function() {
return ngClickOverrideEnabled;
}
};
};
} }
/* global ngTouch: false */ /* global ngTouch: false */
@ -138,6 +41,11 @@ function $TouchProvider($provide, $compileProvider) {
* @ngdoc service * @ngdoc service
* @name $swipe * @name $swipe
* *
* @deprecated
* sinceVersion="1.7.0"
*
* See the {@link ngTouch module} documentation for more information.
*
* @description * @description
* The `$swipe` service is a service that abstracts the messier details of hold-and-drag swipe * The `$swipe` service is a service that abstracts the messier details of hold-and-drag swipe
* behavior, to make implementing swipe-related directives more convenient. * behavior, to make implementing swipe-related directives more convenient.
@ -249,13 +157,17 @@ ngTouch.factory('$swipe', [function() {
totalX = 0; totalX = 0;
totalY = 0; totalY = 0;
lastPos = startCoords; lastPos = startCoords;
eventHandlers['start'] && eventHandlers['start'](startCoords, event); if (eventHandlers['start']) {
eventHandlers['start'](startCoords, event);
}
}); });
var events = getEvents(pointerTypes, 'cancel'); var events = getEvents(pointerTypes, 'cancel');
if (events) { if (events) {
element.on(events, function(event) { element.on(events, function(event) {
active = false; active = false;
eventHandlers['cancel'] && eventHandlers['cancel'](event); if (eventHandlers['cancel']) {
eventHandlers['cancel'](event);
}
}); });
} }
@ -284,325 +196,41 @@ ngTouch.factory('$swipe', [function() {
if (totalY > totalX) { if (totalY > totalX) {
// Allow native scrolling to take over. // Allow native scrolling to take over.
active = false; active = false;
eventHandlers['cancel'] && eventHandlers['cancel'](event); if (eventHandlers['cancel']) {
eventHandlers['cancel'](event);
}
return; return;
} else { } else {
// Prevent the browser from scrolling. // Prevent the browser from scrolling.
event.preventDefault(); event.preventDefault();
eventHandlers['move'] && eventHandlers['move'](coords, event); if (eventHandlers['move']) {
eventHandlers['move'](coords, event);
}
} }
}); });
element.on(getEvents(pointerTypes, 'end'), function(event) { element.on(getEvents(pointerTypes, 'end'), function(event) {
if (!active) return; if (!active) return;
active = false; active = false;
eventHandlers['end'] && eventHandlers['end'](getCoordinates(event), event); if (eventHandlers['end']) {
eventHandlers['end'](getCoordinates(event), event);
}
}); });
} }
}; };
}]); }]);
/* global ngTouch: false,
nodeName_: false
*/
/**
* @ngdoc directive
* @name ngClick
* @deprecated
*
* @description
* <div class="alert alert-danger">
* **DEPRECATION NOTICE**: Beginning with Angular 1.5, this directive is deprecated and by default **disabled**.
* The directive will receive no further support and might be removed from future releases.
* If you need the directive, you can enable it with the {@link ngTouch.$touchProvider $touchProvider#ngClickOverrideEnabled}
* function. We also recommend that you migrate to [FastClick](https://github.com/ftlabs/fastclick).
* To learn more about the 300ms delay, this [Telerik article](http://developer.telerik.com/featured/300-ms-click-delay-ios-8/)
* gives a good overview.
* </div>
* A more powerful replacement for the default ngClick designed to be used on touchscreen
* devices. Most mobile browsers wait about 300ms after a tap-and-release before sending
* the click event. This version handles them immediately, and then prevents the
* following click event from propagating.
*
* Requires the {@link ngTouch `ngTouch`} module to be installed.
*
* This directive can fall back to using an ordinary click event, and so works on desktop
* browsers as well as mobile.
*
* This directive also sets the CSS class `ng-click-active` while the element is being held
* down (by a mouse click or touch) so you can restyle the depressed element if you wish.
*
* @element ANY
* @param {expression} ngClick {@link guide/expression Expression} to evaluate
* upon tap. (Event object is available as `$event`)
*
* @example
<example module="ngClickExample" deps="angular-touch.js">
<file name="index.html">
<button ng-click="count = count + 1" ng-init="count=0">
Increment
</button>
count: {{ count }}
</file>
<file name="script.js">
angular.module('ngClickExample', ['ngTouch']);
</file>
</example>
*/
var ngTouchClickDirectiveFactory = ['$parse', '$timeout', '$rootElement',
function($parse, $timeout, $rootElement) {
var TAP_DURATION = 750; // Shorter than 750ms is a tap, longer is a taphold or drag.
var MOVE_TOLERANCE = 12; // 12px seems to work in most mobile browsers.
var PREVENT_DURATION = 2500; // 2.5 seconds maximum from preventGhostClick call to click
var CLICKBUSTER_THRESHOLD = 25; // 25 pixels in any dimension is the limit for busting clicks.
var ACTIVE_CLASS_NAME = 'ng-click-active';
var lastPreventedTime;
var touchCoordinates;
var lastLabelClickCoordinates;
// TAP EVENTS AND GHOST CLICKS
//
// Why tap events?
// Mobile browsers detect a tap, then wait a moment (usually ~300ms) to see if you're
// double-tapping, and then fire a click event.
//
// This delay sucks and makes mobile apps feel unresponsive.
// So we detect touchstart, touchcancel and touchend ourselves and determine when
// the user has tapped on something.
//
// What happens when the browser then generates a click event?
// The browser, of course, also detects the tap and fires a click after a delay. This results in
// tapping/clicking twice. We do "clickbusting" to prevent it.
//
// How does it work?
// We attach global touchstart and click handlers, that run during the capture (early) phase.
// So the sequence for a tap is:
// - global touchstart: Sets an "allowable region" at the point touched.
// - element's touchstart: Starts a touch
// (- touchcancel ends the touch, no click follows)
// - element's touchend: Determines if the tap is valid (didn't move too far away, didn't hold
// too long) and fires the user's tap handler. The touchend also calls preventGhostClick().
// - preventGhostClick() removes the allowable region the global touchstart created.
// - The browser generates a click event.
// - The global click handler catches the click, and checks whether it was in an allowable region.
// - If preventGhostClick was called, the region will have been removed, the click is busted.
// - If the region is still there, the click proceeds normally. Therefore clicks on links and
// other elements without ngTap on them work normally.
//
// This is an ugly, terrible hack!
// Yeah, tell me about it. The alternatives are using the slow click events, or making our users
// deal with the ghost clicks, so I consider this the least of evils. Fortunately Angular
// encapsulates this ugly logic away from the user.
//
// Why not just put click handlers on the element?
// We do that too, just to be sure. If the tap event caused the DOM to change,
// it is possible another element is now in that position. To take account for these possibly
// distinct elements, the handlers are global and care only about coordinates.
// Checks if the coordinates are close enough to be within the region.
function hit(x1, y1, x2, y2) {
return Math.abs(x1 - x2) < CLICKBUSTER_THRESHOLD && Math.abs(y1 - y2) < CLICKBUSTER_THRESHOLD;
}
// Checks a list of allowable regions against a click location.
// Returns true if the click should be allowed.
// Splices out the allowable region from the list after it has been used.
function checkAllowableRegions(touchCoordinates, x, y) {
for (var i = 0; i < touchCoordinates.length; i += 2) {
if (hit(touchCoordinates[i], touchCoordinates[i + 1], x, y)) {
touchCoordinates.splice(i, i + 2);
return true; // allowable region
}
}
return false; // No allowable region; bust it.
}
// Global click handler that prevents the click if it's in a bustable zone and preventGhostClick
// was called recently.
function onClick(event) {
if (Date.now() - lastPreventedTime > PREVENT_DURATION) {
return; // Too old.
}
var touches = event.touches && event.touches.length ? event.touches : [event];
var x = touches[0].clientX;
var y = touches[0].clientY;
// Work around desktop Webkit quirk where clicking a label will fire two clicks (on the label
// and on the input element). Depending on the exact browser, this second click we don't want
// to bust has either (0,0), negative coordinates, or coordinates equal to triggering label
// click event
if (x < 1 && y < 1) {
return; // offscreen
}
if (lastLabelClickCoordinates &&
lastLabelClickCoordinates[0] === x && lastLabelClickCoordinates[1] === y) {
return; // input click triggered by label click
}
// reset label click coordinates on first subsequent click
if (lastLabelClickCoordinates) {
lastLabelClickCoordinates = null;
}
// remember label click coordinates to prevent click busting of trigger click event on input
if (nodeName_(event.target) === 'label') {
lastLabelClickCoordinates = [x, y];
}
// Look for an allowable region containing this click.
// If we find one, that means it was created by touchstart and not removed by
// preventGhostClick, so we don't bust it.
if (checkAllowableRegions(touchCoordinates, x, y)) {
return;
}
// If we didn't find an allowable region, bust the click.
event.stopPropagation();
event.preventDefault();
// Blur focused form elements
event.target && event.target.blur && event.target.blur();
}
// Global touchstart handler that creates an allowable region for a click event.
// This allowable region can be removed by preventGhostClick if we want to bust it.
function onTouchStart(event) {
var touches = event.touches && event.touches.length ? event.touches : [event];
var x = touches[0].clientX;
var y = touches[0].clientY;
touchCoordinates.push(x, y);
$timeout(function() {
// Remove the allowable region.
for (var i = 0; i < touchCoordinates.length; i += 2) {
if (touchCoordinates[i] == x && touchCoordinates[i + 1] == y) {
touchCoordinates.splice(i, i + 2);
return;
}
}
}, PREVENT_DURATION, false);
}
// On the first call, attaches some event handlers. Then whenever it gets called, it creates a
// zone around the touchstart where clicks will get busted.
function preventGhostClick(x, y) {
if (!touchCoordinates) {
$rootElement[0].addEventListener('click', onClick, true);
$rootElement[0].addEventListener('touchstart', onTouchStart, true);
touchCoordinates = [];
}
lastPreventedTime = Date.now();
checkAllowableRegions(touchCoordinates, x, y);
}
// Actual linking function.
return function(scope, element, attr) {
var clickHandler = $parse(attr.ngClick),
tapping = false,
tapElement, // Used to blur the element after a tap.
startTime, // Used to check if the tap was held too long.
touchStartX,
touchStartY;
function resetState() {
tapping = false;
element.removeClass(ACTIVE_CLASS_NAME);
}
element.on('touchstart', function(event) {
tapping = true;
tapElement = event.target ? event.target : event.srcElement; // IE uses srcElement.
// Hack for Safari, which can target text nodes instead of containers.
if (tapElement.nodeType == 3) {
tapElement = tapElement.parentNode;
}
element.addClass(ACTIVE_CLASS_NAME);
startTime = Date.now();
// Use jQuery originalEvent
var originalEvent = event.originalEvent || event;
var touches = originalEvent.touches && originalEvent.touches.length ? originalEvent.touches : [originalEvent];
var e = touches[0];
touchStartX = e.clientX;
touchStartY = e.clientY;
});
element.on('touchcancel', function(event) {
resetState();
});
element.on('touchend', function(event) {
var diff = Date.now() - startTime;
// Use jQuery originalEvent
var originalEvent = event.originalEvent || event;
var touches = (originalEvent.changedTouches && originalEvent.changedTouches.length) ?
originalEvent.changedTouches :
((originalEvent.touches && originalEvent.touches.length) ? originalEvent.touches : [originalEvent]);
var e = touches[0];
var x = e.clientX;
var y = e.clientY;
var dist = Math.sqrt(Math.pow(x - touchStartX, 2) + Math.pow(y - touchStartY, 2));
if (tapping && diff < TAP_DURATION && dist < MOVE_TOLERANCE) {
// Call preventGhostClick so the clickbuster will catch the corresponding click.
preventGhostClick(x, y);
// Blur the focused element (the button, probably) before firing the callback.
// This doesn't work perfectly on Android Chrome, but seems to work elsewhere.
// I couldn't get anything to work reliably on Android Chrome.
if (tapElement) {
tapElement.blur();
}
if (!angular.isDefined(attr.disabled) || attr.disabled === false) {
element.triggerHandler('click', [event]);
}
}
resetState();
});
// Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click
// something else nearby.
element.onclick = function(event) { };
// Actual click handler.
// There are three different kinds of clicks, only two of which reach this point.
// - On desktop browsers without touch events, their clicks will always come here.
// - On mobile browsers, the simulated "fast" click will call this.
// - But the browser's follow-up slow click will be "busted" before it reaches this handler.
// Therefore it's safe to use this directive on both mobile and desktop.
element.on('click', function(event, touchend) {
scope.$apply(function() {
clickHandler(scope, {$event: (touchend || event)});
});
});
element.on('mousedown', function(event) {
element.addClass(ACTIVE_CLASS_NAME);
});
element.on('mousemove mouseup', function(event) {
element.removeClass(ACTIVE_CLASS_NAME);
});
};
}];
/* global ngTouch: false */ /* global ngTouch: false */
/** /**
* @ngdoc directive * @ngdoc directive
* @name ngSwipeLeft * @name ngSwipeLeft
* *
* @deprecated
* sinceVersion="1.7.0"
*
* See the {@link ngTouch module} documentation for more information.
*
* @description * @description
* Specify custom behavior when an element is swiped to the left on a touchscreen device. * Specify custom behavior when an element is swiped to the left on a touchscreen device.
* A leftward swipe is a quick, right-to-left slide of the finger. * A leftward swipe is a quick, right-to-left slide of the finger.
@ -619,7 +247,7 @@ var ngTouchClickDirectiveFactory = ['$parse', '$timeout', '$rootElement',
* upon left swipe. (Event object is available as `$event`) * upon left swipe. (Event object is available as `$event`)
* *
* @example * @example
<example module="ngSwipeLeftExample" deps="angular-touch.js"> <example module="ngSwipeLeftExample" deps="angular-touch.js" name="ng-swipe-left">
<file name="index.html"> <file name="index.html">
<div ng-show="!showActions" ng-swipe-left="showActions = true"> <div ng-show="!showActions" ng-swipe-left="showActions = true">
Some list content, like an email in the inbox Some list content, like an email in the inbox
@ -639,6 +267,11 @@ var ngTouchClickDirectiveFactory = ['$parse', '$timeout', '$rootElement',
* @ngdoc directive * @ngdoc directive
* @name ngSwipeRight * @name ngSwipeRight
* *
* @deprecated
* sinceVersion="1.7.0"
*
* See the {@link ngTouch module} documentation for more information.
*
* @description * @description
* Specify custom behavior when an element is swiped to the right on a touchscreen device. * Specify custom behavior when an element is swiped to the right on a touchscreen device.
* A rightward swipe is a quick, left-to-right slide of the finger. * A rightward swipe is a quick, left-to-right slide of the finger.
@ -652,7 +285,7 @@ var ngTouchClickDirectiveFactory = ['$parse', '$timeout', '$rootElement',
* upon right swipe. (Event object is available as `$event`) * upon right swipe. (Event object is available as `$event`)
* *
* @example * @example
<example module="ngSwipeRightExample" deps="angular-touch.js"> <example module="ngSwipeRightExample" deps="angular-touch.js" name="ng-swipe-right">
<file name="index.html"> <file name="index.html">
<div ng-show="!showActions" ng-swipe-left="showActions = true"> <div ng-show="!showActions" ng-swipe-left="showActions = true">
Some list content, like an email in the inbox Some list content, like an email in the inbox

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"raw":"v1.5.8","major":1,"minor":5,"patch":8,"prerelease":[],"build":[],"version":"1.5.8","codeName":"arbitrary-fallbacks","full":"1.5.8","branch":"v1.5.x","cdn":{"raw":"v1.5.7","major":1,"minor":5,"patch":7,"prerelease":[],"build":[],"version":"1.5.7","docsUrl":"http://code.angularjs.org/1.5.7/docs"}} {"raw":"v1.8.2","major":1,"minor":8,"patch":2,"prerelease":[],"build":[],"version":"1.8.2","codeName":"meteoric-mining","full":"1.8.2","branch":"v1.8.x","cdn":{"raw":"v1.8.2","major":1,"minor":8,"patch":2,"prerelease":[],"build":[],"version":"1.8.2","docsUrl":"http://code.angularjs.org/1.8.2/docs"}}

View File

@ -1 +1 @@
1.5.8 1.8.2