diff --git a/.gitignore b/.gitignore
index bf5d094f0c..443e12ad50 100644
--- a/.gitignore
+++ b/.gitignore
@@ -52,4 +52,6 @@ target
# nodejs #
##########
-node_modules
\ No newline at end of file
+# KEYCLOAK-5391: We will re-exclude node_modules when node_modules handling is worked out.
+# For now, we keep our js libraries checked into GitHub, so we don't ignore.
+#node_modules
\ No newline at end of file
diff --git a/themes/pom.xml b/themes/pom.xml
index 0b98fc3ae2..a0a5670600 100755
--- a/themes/pom.xml
+++ b/themes/pom.xml
@@ -53,6 +53,7 @@
+
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/README.md b/themes/src/main/resources/theme/keycloak/common/resources/README.md
index a5ef852162..50e2e69551 100644
--- a/themes/src/main/resources/theme/keycloak/common/resources/README.md
+++ b/themes/src/main/resources/theme/keycloak/common/resources/README.md
@@ -6,7 +6,9 @@ libraries are not available in the public npm repo and are thus checked into
GitHub.
Javascript libraries under *./node_modules* directory are managed with yarn.
-THEY SHOULD NOT BE CHECKED INTO GITHUB!
+
+THESE LIBRARIES SHOULD BE CHECKED INTO GITHUB UNTIL KEYCLOAK-5324 and KEYCLOAK-5392
+ARE RESOLVED.
Adding or Removing javascript libraries
---------------------------------------
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/.bin/mkdirp b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/.bin/mkdirp
new file mode 100644
index 0000000000..4b0046722f
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/.bin/mkdirp
@@ -0,0 +1,15 @@
+#!/bin/sh
+basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
+
+case `uname` in
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
+esac
+
+if [ -x "$basedir/node" ]; then
+ "$basedir/node" "$basedir/../mkdirp/bin/cmd.js" "$@"
+ ret=$?
+else
+ node "$basedir/../mkdirp/bin/cmd.js" "$@"
+ ret=$?
+fi
+exit $ret
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/.bin/mkdirp.cmd b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/.bin/mkdirp.cmd
new file mode 100644
index 0000000000..0d2cdd7c48
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/.bin/mkdirp.cmd
@@ -0,0 +1,7 @@
+@IF EXIST "%~dp0\node.exe" (
+ "%~dp0\node.exe" "%~dp0\..\mkdirp\bin\cmd.js" %*
+) ELSE (
+ @SETLOCAL
+ @SET PATHEXT=%PATHEXT:;.JS;=;%
+ node "%~dp0\..\mkdirp\bin\cmd.js" %*
+)
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/.yarn-integrity b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/.yarn-integrity
new file mode 100644
index 0000000000..5176ac33da
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/.yarn-integrity
@@ -0,0 +1,49 @@
+{
+ "flags": [],
+ "linkedModules": [],
+ "topLevelPatters": [
+ "angular-cookies@^1.6.4",
+ "angular-loader@^1.6.4",
+ "angular-resource@^1.6.4",
+ "angular-route@^1.6.4",
+ "angular-sanitize@^1.6.4",
+ "angular-translate-loader-url@^2.15.1",
+ "angular-translate@^2.15.1",
+ "angular-treeview@^0.1.5",
+ "angular-ui-select2@^0.0.5",
+ "angular@^1.6.4",
+ "autofill-event@^0.0.1",
+ "bootstrap@^3.3.7",
+ "filesaver@^0.0.13",
+ "font-awesome@^4.7.0",
+ "jquery@^3.2.1",
+ "ng-file-upload@^12.2.13",
+ "select2@3.5.1"
+ ],
+ "lockfileEntries": {
+ "angular-cookies@^1.6.4": "https://registry.yarnpkg.com/angular-cookies/-/angular-cookies-1.6.4.tgz#c28f3f6aac7a9826c1e45f1d6807240036e5b26d",
+ "angular-loader@^1.6.4": "https://registry.yarnpkg.com/angular-loader/-/angular-loader-1.6.4.tgz#c202b9dd233b11e66c802f7716c5d82ad249bb42",
+ "angular-resource@^1.6.4": "https://registry.yarnpkg.com/angular-resource/-/angular-resource-1.6.4.tgz#bcb83688b0a7d3402fde58dc7f4881383a6c0ebb",
+ "angular-route@^1.6.4": "https://registry.yarnpkg.com/angular-route/-/angular-route-1.6.4.tgz#7bb216fcda746a1b8c452054b05900a7074ecc62",
+ "angular-sanitize@^1.6.4": "https://registry.yarnpkg.com/angular-sanitize/-/angular-sanitize-1.6.4.tgz#60a37ea96fb0d4a322a3ccb64ee4a5cf3b154f0c",
+ "angular-translate-loader-url@^2.15.1": "https://registry.yarnpkg.com/angular-translate-loader-url/-/angular-translate-loader-url-2.15.1.tgz#31d6785a59d813fe7d8a1990d8be16864ff59e24",
+ "angular-translate@^2.15.1": "https://registry.yarnpkg.com/angular-translate/-/angular-translate-2.15.1.tgz#920f7d2b877819e1c0fa881781b9b675f36480ce",
+ "angular-translate@~2.15.1": "https://registry.yarnpkg.com/angular-translate/-/angular-translate-2.15.1.tgz#920f7d2b877819e1c0fa881781b9b675f36480ce",
+ "angular-treeview@^0.1.5": "https://registry.yarnpkg.com/angular-treeview/-/angular-treeview-0.1.5.tgz#ec797d4d001b20172c983e65d855ebcd8152b4fa",
+ "angular-ui-select2@^0.0.5": "https://registry.yarnpkg.com/angular-ui-select2/-/angular-ui-select2-0.0.5.tgz#15e7643afd69ca9063d405eb3be2f95dd5ec87f5",
+ "angular@>=1.2.26 <=1.6": "https://registry.yarnpkg.com/angular/-/angular-1.6.4.tgz#03b7b15c01a0802d7e2cf593240e604054dc77fb",
+ "angular@^1.6.4": "https://registry.yarnpkg.com/angular/-/angular-1.6.4.tgz#03b7b15c01a0802d7e2cf593240e604054dc77fb",
+ "autofill-event@^0.0.1": "https://registry.yarnpkg.com/autofill-event/-/autofill-event-0.0.1.tgz#c382cf989b21b61ff4a12b3597e1943471d3cf7a",
+ "bootstrap@^3.3.7": "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.3.7.tgz#5a389394549f23330875a3b150656574f8a9eb71",
+ "filesaver@^0.0.13": "https://registry.yarnpkg.com/filesaver/-/filesaver-0.0.13.tgz#fa9b2ac1371d436fe5edc9285ed998d1e2782bee",
+ "font-awesome@^4.7.0": "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133",
+ "jquery@^3.2.1": "https://registry.yarnpkg.com/jquery/-/jquery-3.2.1.tgz#5c4d9de652af6cd0a770154a631bba12b015c787",
+ "minimist@0.0.8": "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d",
+ "mkdirp@^0.5.0": "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903",
+ "ng-file-upload@^12.2.13": "https://registry.yarnpkg.com/ng-file-upload/-/ng-file-upload-12.2.13.tgz#01800f3872e526f95310f8477e99e4f12d0d8d14",
+ "safename@0.0.4": "https://registry.yarnpkg.com/safename/-/safename-0.0.4.tgz#b82c3b6db70d943a0582f9052fbfbfebbb589af5",
+ "select2@3.5.1": "https://registry.yarnpkg.com/select2/-/select2-3.5.1.tgz#f2819489bbc65fd6d328be72bbe2b95dd7e87cfe"
+ },
+ "files": [],
+ "artifacts": {}
+}
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/LICENSE.md b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/LICENSE.md
new file mode 100644
index 0000000000..2c395eef1b
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Angular
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/README.md b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/README.md
new file mode 100644
index 0000000000..7b190d3461
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/README.md
@@ -0,0 +1,68 @@
+# packaged angular-cookies
+
+This repo is for distribution on `npm` and `bower`. The source for this module is in the
+[main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngCookies).
+Please file issues and pull requests against that repo.
+
+## Install
+
+You can install this package either with `npm` or with `bower`.
+
+### npm
+
+```shell
+npm install angular-cookies
+```
+
+Then add `ngCookies` as a dependency for your app:
+
+```javascript
+angular.module('myApp', [require('angular-cookies')]);
+```
+
+### bower
+
+```shell
+bower install angular-cookies
+```
+
+Add a `
+```
+
+Then add `ngCookies` as a dependency for your app:
+
+```javascript
+angular.module('myApp', ['ngCookies']);
+```
+
+## Documentation
+
+Documentation is available on the
+[AngularJS docs site](http://docs.angularjs.org/api/ngCookies).
+
+## License
+
+The MIT License
+
+Copyright (c) 2010-2015 Google, Inc. http://angularjs.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/angular-cookies.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/angular-cookies.js
new file mode 100644
index 0000000000..a290ec0460
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/angular-cookies.js
@@ -0,0 +1,331 @@
+/**
+ * @license AngularJS v1.6.4
+ * (c) 2010-2017 Google, Inc. http://angularjs.org
+ * License: MIT
+ */
+(function(window, angular) {'use strict';
+
+/**
+ * @ngdoc module
+ * @name ngCookies
+ * @description
+ *
+ * # ngCookies
+ *
+ * The `ngCookies` module provides a convenient wrapper for reading and writing browser cookies.
+ *
+ *
+ *
+ *
+ * See {@link ngCookies.$cookies `$cookies`} for usage.
+ */
+
+
+angular.module('ngCookies', ['ng']).
+ info({ angularVersion: '1.6.4' }).
+ /**
+ * @ngdoc provider
+ * @name $cookiesProvider
+ * @description
+ * Use `$cookiesProvider` to change the default behavior of the {@link ngCookies.$cookies $cookies} service.
+ * */
+ provider('$cookies', [/** @this */function $CookiesProvider() {
+ /**
+ * @ngdoc property
+ * @name $cookiesProvider#defaults
+ * @description
+ *
+ * Object containing default options to pass when setting cookies.
+ *
+ * The object may have following properties:
+ *
+ * - **path** - `{string}` - The cookie will be available only for this path and its
+ * sub-paths. By default, this is the URL that appears in your ` ` tag.
+ * - **domain** - `{string}` - The cookie will be available only for this domain and
+ * its sub-domains. For security reasons the user agent will not accept the cookie
+ * if the current domain is not a sub-domain of this domain or equal to it.
+ * - **expires** - `{string|Date}` - String of the form "Wdy, DD Mon YYYY HH:MM:SS GMT"
+ * 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
+ * secured connection.
+ *
+ * Note: By default, the address that appears in your ` ` tag will be used as the path.
+ * 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 = {};
+
+ function calcOptions(options) {
+ return options ? angular.extend({}, defaults, options) : defaults;
+ }
+
+ /**
+ * @ngdoc service
+ * @name $cookies
+ *
+ * @description
+ * Provides read/write access to browser's cookies.
+ *
+ *
+ * Up until Angular 1.3, `$cookies` exposed properties that represented the
+ * current browser cookie values. In version 1.4, this behavior has changed, and
+ * `$cookies` now provides a standard api of getters, setters etc.
+ *
+ *
+ * Requires the {@link ngCookies `ngCookies`} module to be installed.
+ *
+ * @example
+ *
+ * ```js
+ * angular.module('cookiesExample', ['ngCookies'])
+ * .controller('ExampleController', ['$cookies', function($cookies) {
+ * // Retrieving a cookie
+ * var favoriteCookie = $cookies.get('myFavorite');
+ * // Setting a cookie
+ * $cookies.put('myFavorite', 'oatmeal');
+ * }]);
+ * ```
+ */
+ this.$get = ['$$cookieReader', '$$cookieWriter', function($$cookieReader, $$cookieWriter) {
+ return {
+ /**
+ * @ngdoc method
+ * @name $cookies#get
+ *
+ * @description
+ * Returns the value of given cookie key
+ *
+ * @param {string} key Id to use for lookup.
+ * @returns {string} Raw cookie value.
+ */
+ get: function(key) {
+ return $$cookieReader()[key];
+ },
+
+ /**
+ * @ngdoc method
+ * @name $cookies#getObject
+ *
+ * @description
+ * Returns the deserialized value of given cookie key
+ *
+ * @param {string} key Id to use for lookup.
+ * @returns {Object} Deserialized cookie value.
+ */
+ getObject: function(key) {
+ var value = this.get(key);
+ return value ? angular.fromJson(value) : value;
+ },
+
+ /**
+ * @ngdoc method
+ * @name $cookies#getAll
+ *
+ * @description
+ * Returns a key value object with all the cookies
+ *
+ * @returns {Object} All cookies
+ */
+ getAll: function() {
+ return $$cookieReader();
+ },
+
+ /**
+ * @ngdoc method
+ * @name $cookies#put
+ *
+ * @description
+ * Sets a value for given cookie key
+ *
+ * @param {string} key Id for the `value`.
+ * @param {string} value Raw value to be stored.
+ * @param {Object=} options Options object.
+ * See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults}
+ */
+ put: function(key, value, options) {
+ $$cookieWriter(key, value, calcOptions(options));
+ },
+
+ /**
+ * @ngdoc method
+ * @name $cookies#putObject
+ *
+ * @description
+ * Serializes and sets a value for given cookie key
+ *
+ * @param {string} key Id for the `value`.
+ * @param {Object} value Value to be stored.
+ * @param {Object=} options Options object.
+ * See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults}
+ */
+ putObject: function(key, value, options) {
+ this.put(key, angular.toJson(value), options);
+ },
+
+ /**
+ * @ngdoc method
+ * @name $cookies#remove
+ *
+ * @description
+ * Remove given cookie
+ *
+ * @param {string} key Id of the key-value pair to delete.
+ * @param {Object=} options Options object.
+ * See {@link ngCookies.$cookiesProvider#defaults $cookiesProvider.defaults}
+ */
+ remove: function(key, options) {
+ $$cookieWriter(key, undefined, calcOptions(options));
+ }
+ };
+ }];
+ }]);
+
+angular.module('ngCookies').
+/**
+ * @ngdoc service
+ * @name $cookieStore
+ * @deprecated
+ * sinceVersion="v1.4.0"
+ * Please use the {@link ngCookies.$cookies `$cookies`} service instead.
+ *
+ * @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.
+ *
+ * @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
+ * @requires $document
+ *
+ * @description
+ * This is a private service for writing cookies
+ *
+ * @param {string} name Cookie name
+ * @param {string=} value Cookie value (if undefined, cookie will be deleted)
+ * @param {Object=} options Object with options that need to be stored for the cookie.
+ */
+function $$CookieWriter($document, $log, $browser) {
+ var cookiePath = $browser.baseHref();
+ var rawDocument = $document[0];
+
+ function buildCookieString(name, value, options) {
+ var path, expires;
+ options = options || {};
+ expires = options.expires;
+ path = angular.isDefined(options.path) ? options.path : cookiePath;
+ if (angular.isUndefined(value)) {
+ expires = 'Thu, 01 Jan 1970 00:00:00 GMT';
+ value = '';
+ }
+ if (angular.isString(expires)) {
+ expires = new Date(expires);
+ }
+
+ var str = encodeURIComponent(name) + '=' + encodeURIComponent(value);
+ str += path ? ';path=' + path : '';
+ str += options.domain ? ';domain=' + options.domain : '';
+ str += expires ? ';expires=' + expires.toUTCString() : '';
+ str += options.secure ? ';secure' : '';
+
+ // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum:
+ // - 300 cookies
+ // - 20 cookies per unique domain
+ // - 4096 bytes per cookie
+ var cookieLength = str.length + 1;
+ if (cookieLength > 4096) {
+ $log.warn('Cookie \'' + name +
+ '\' possibly not set or overflowed because it was too large (' +
+ cookieLength + ' > 4096 bytes)!');
+ }
+
+ return str;
+ }
+
+ return function(name, value, options) {
+ rawDocument.cookie = buildCookieString(name, value, options);
+ };
+}
+
+$$CookieWriter.$inject = ['$document', '$log', '$browser'];
+
+angular.module('ngCookies').provider('$$cookieWriter', /** @this */ function $$CookieWriterProvider() {
+ this.$get = $$CookieWriter;
+});
+
+
+})(window, window.angular);
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/angular-cookies.min.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/angular-cookies.min.js
new file mode 100644
index 0000000000..dd30241caa
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/angular-cookies.min.js
@@ -0,0 +1,9 @@
+/*
+ AngularJS v1.6.4
+ (c) 2010-2017 Google, Inc. http://angularjs.org
+ License: MIT
+*/
+(function(n,c){'use strict';function l(b,a,g){var d=g.baseHref(),k=b[0];return function(b,e,f){var g,h;f=f||{};h=f.expires;g=c.isDefined(f.path)?f.path:d;c.isUndefined(e)&&(h="Thu, 01 Jan 1970 00:00:00 GMT",e="");c.isString(h)&&(h=new Date(h));e=encodeURIComponent(b)+"="+encodeURIComponent(e);e=e+(g?";path="+g:"")+(f.domain?";domain="+f.domain:"");e+=h?";expires="+h.toUTCString():"";e+=f.secure?";secure":"";f=e.length+1;4096 4096 bytes)!");k.cookie=e}}c.module("ngCookies",["ng"]).info({angularVersion:"1.6.4"}).provider("$cookies",[function(){var b=this.defaults={};this.$get=["$$cookieReader","$$cookieWriter",function(a,g){return{get:function(d){return a()[d]},getObject:function(d){return(d=this.get(d))?c.fromJson(d):d},getAll:function(){return a()},put:function(d,a,m){g(d,a,m?c.extend({},b,m):b)},putObject:function(d,b,a){this.put(d,c.toJson(b),a)},remove:function(a,k){g(a,void 0,k?c.extend({},b,k):b)}}}]}]);c.module("ngCookies").factory("$cookieStore",
+["$cookies",function(b){return{get:function(a){return b.getObject(a)},put:function(a,c){b.putObject(a,c)},remove:function(a){b.remove(a)}}}]);l.$inject=["$document","$log","$browser"];c.module("ngCookies").provider("$$cookieWriter",function(){this.$get=l})})(window,window.angular);
+//# sourceMappingURL=angular-cookies.min.js.map
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/angular-cookies.min.js.map b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/angular-cookies.min.js.map
new file mode 100644
index 0000000000..a4278c4d39
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/angular-cookies.min.js.map
@@ -0,0 +1,8 @@
+{
+"version":3,
+"file":"angular-cookies.min.js",
+"lineCount":8,
+"mappings":"A;;;;;aAKC,SAAQ,CAACA,CAAD,CAASC,CAAT,CAAkB,CAoR3BC,QAASA,EAAc,CAACC,CAAD,CAAYC,CAAZ,CAAkBC,CAAlB,CAA4B,CACjD,IAAIC,EAAaD,CAAAE,SAAA,EAAjB,CACIC,EAAcL,CAAA,CAAU,CAAV,CAmClB,OAAO,SAAQ,CAACM,CAAD,CAAOC,CAAP,CAAcC,CAAd,CAAuB,CAjCW,IAC3CC,CAD2C,CACrCC,CACVF,EAAA,CAgCoDA,CAhCpD,EAAqB,EACrBE,EAAA,CAAUF,CAAAE,QACVD,EAAA,CAAOX,CAAAa,UAAA,CAAkBH,CAAAC,KAAlB,CAAA,CAAkCD,CAAAC,KAAlC,CAAiDN,CACpDL,EAAAc,YAAA,CAAoBL,CAApB,CAAJ,GACEG,CACA,CADU,+BACV,CAAAH,CAAA,CAAQ,EAFV,CAIIT,EAAAe,SAAA,CAAiBH,CAAjB,CAAJ,GACEA,CADF,CACY,IAAII,IAAJ,CAASJ,CAAT,CADZ,CAIIK,EAAAA,CAAMC,kBAAA,CAqB6BV,CArB7B,CAANS,CAAiC,GAAjCA,CAAuCC,kBAAA,CAAmBT,CAAnB,CAE3CQ,EAAA,CADAA,CACA,EADON,CAAA,CAAO,QAAP,CAAkBA,CAAlB,CAAyB,EAChC,GAAOD,CAAAS,OAAA,CAAiB,UAAjB,CAA8BT,CAAAS,OAA9B,CAA+C,EAAtD,CACAF,EAAA,EAAOL,CAAA,CAAU,WAAV,CAAwBA,CAAAQ,YAAA,EAAxB,CAAgD,EACvDH,EAAA,EAAOP,CAAAW,OAAA,CAAiB,SAAjB,CAA6B,EAMhCC,EAAAA,CAAeL,CAAAM,OAAfD,CAA4B,CACb,KAAnB,CAAIA,CAAJ,EACEnB,CAAAqB,KAAA,CAAU,UAAV,CASqChB,CATrC,CACE,6DADF;AAEEc,CAFF,CAEiB,iBAFjB,CASFf,EAAAkB,OAAA,CAJOR,CAG6B,CArCW,CAlQnDjB,CAAA0B,OAAA,CAAe,WAAf,CAA4B,CAAC,IAAD,CAA5B,CAAAC,KAAA,CACO,CAAEC,eAAgB,OAAlB,CADP,CAAAC,SAAA,CAQY,UARZ,CAQwB,CAAaC,QAAyB,EAAG,CAkC7D,IAAIC,EAAW,IAAAA,SAAXA,CAA2B,EAiC/B,KAAAC,KAAA,CAAY,CAAC,gBAAD,CAAmB,gBAAnB,CAAqC,QAAQ,CAACC,CAAD,CAAiBC,CAAjB,CAAiC,CACxF,MAAO,CAWLC,IAAKA,QAAQ,CAACC,CAAD,CAAM,CACjB,MAAOH,EAAA,EAAA,CAAiBG,CAAjB,CADU,CAXd,CAyBLC,UAAWA,QAAQ,CAACD,CAAD,CAAM,CAEvB,MAAO,CADH3B,CACG,CADK,IAAA0B,IAAA,CAASC,CAAT,CACL,EAAQpC,CAAAsC,SAAA,CAAiB7B,CAAjB,CAAR,CAAkCA,CAFlB,CAzBpB,CAuCL8B,OAAQA,QAAQ,EAAG,CACjB,MAAON,EAAA,EADU,CAvCd,CAuDLO,IAAKA,QAAQ,CAACJ,CAAD,CAAM3B,CAAN,CAAaC,CAAb,CAAsB,CACjCwB,CAAA,CAAeE,CAAf,CAAoB3B,CAApB,CAAuCC,CAvFpC,CAAUV,CAAAyC,OAAA,CAAe,EAAf,CAAmBV,CAAnB,CAuF0BrB,CAvF1B,CAAV,CAAkDqB,CAuFrD,CADiC,CAvD9B,CAuELW,UAAWA,QAAQ,CAACN,CAAD,CAAM3B,CAAN,CAAaC,CAAb,CAAsB,CACvC,IAAA8B,IAAA,CAASJ,CAAT,CAAcpC,CAAA2C,OAAA,CAAelC,CAAf,CAAd,CAAqCC,CAArC,CADuC,CAvEpC,CAsFLkC,OAAQA,QAAQ,CAACR,CAAD,CAAM1B,CAAN,CAAe,CAC7BwB,CAAA,CAAeE,CAAf,CAAoBS,IAAAA,EAApB,CAA2CnC,CAtHxC,CAAUV,CAAAyC,OAAA,CAAe,EAAf,CAAmBV,CAAnB,CAsH8BrB,CAtH9B,CAAV,CAAkDqB,CAsHrD,CAD6B,CAtF1B,CADiF,CAA9E,CAnEiD,CAAzC,CARxB,CAyKA/B,EAAA0B,OAAA,CAAe,WAAf,CAAAoB,QAAA,CA+BS,cA/BT;AA+ByB,CAAC,UAAD,CAAa,QAAQ,CAACC,CAAD,CAAW,CAErD,MAAO,CAWLZ,IAAKA,QAAQ,CAACC,CAAD,CAAM,CACjB,MAAOW,EAAAV,UAAA,CAAmBD,CAAnB,CADU,CAXd,CAyBLI,IAAKA,QAAQ,CAACJ,CAAD,CAAM3B,CAAN,CAAa,CACxBsC,CAAAL,UAAA,CAAmBN,CAAnB,CAAwB3B,CAAxB,CADwB,CAzBrB,CAsCLmC,OAAQA,QAAQ,CAACR,CAAD,CAAM,CACpBW,CAAAH,OAAA,CAAgBR,CAAhB,CADoB,CAtCjB,CAF8C,CAAhC,CA/BzB,CAmIAnC,EAAA+C,QAAA,CAAyB,CAAC,WAAD,CAAc,MAAd,CAAsB,UAAtB,CAEzBhD,EAAA0B,OAAA,CAAe,WAAf,CAAAG,SAAA,CAAqC,gBAArC,CAAoEoB,QAA+B,EAAG,CACpG,IAAAjB,KAAA,CAAY/B,CADwF,CAAtG,CAhU2B,CAA1B,CAAD,CAqUGF,MArUH,CAqUWA,MAAAC,QArUX;",
+"sources":["angular-cookies.js"],
+"names":["window","angular","$$CookieWriter","$document","$log","$browser","cookiePath","baseHref","rawDocument","name","value","options","path","expires","isDefined","isUndefined","isString","Date","str","encodeURIComponent","domain","toUTCString","secure","cookieLength","length","warn","cookie","module","info","angularVersion","provider","$CookiesProvider","defaults","$get","$$cookieReader","$$cookieWriter","get","key","getObject","fromJson","getAll","put","extend","putObject","toJson","remove","undefined","factory","$cookies","$inject","$$CookieWriterProvider"]
+}
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/bower.json b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/bower.json
new file mode 100644
index 0000000000..1f522e355b
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/bower.json
@@ -0,0 +1,10 @@
+{
+ "name": "angular-cookies",
+ "version": "1.6.4",
+ "license": "MIT",
+ "main": "./angular-cookies.js",
+ "ignore": [],
+ "dependencies": {
+ "angular": "1.6.4"
+ }
+}
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/index.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/index.js
new file mode 100644
index 0000000000..657667549a
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/index.js
@@ -0,0 +1,2 @@
+require('./angular-cookies');
+module.exports = 'ngCookies';
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/package.json b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/package.json
new file mode 100644
index 0000000000..5f3597b18a
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-cookies/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "angular-cookies",
+ "version": "1.6.4",
+ "description": "AngularJS module for cookies",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/angular/angular.js.git"
+ },
+ "keywords": [
+ "angular",
+ "framework",
+ "browser",
+ "cookies",
+ "client-side"
+ ],
+ "author": "Angular Core Team ",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/angular/angular.js/issues"
+ },
+ "homepage": "http://angularjs.org",
+ "jspm": {
+ "shim": {
+ "angular-cookies": {
+ "deps": ["angular"]
+ }
+ }
+ }
+}
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-loader/LICENSE.md b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-loader/LICENSE.md
new file mode 100644
index 0000000000..2c395eef1b
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-loader/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Angular
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-loader/README.md b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-loader/README.md
new file mode 100644
index 0000000000..7322047be0
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-loader/README.md
@@ -0,0 +1,65 @@
+# packaged angular-loader
+
+This repo is for distribution on `npm` and `bower`. The source for this module is in the
+[main AngularJS repo](https://github.com/angular/angular.js/blob/master/src/loader.js).
+Please file issues and pull requests against that repo.
+
+## Install
+
+You can install this package either with `npm` or with `bower`.
+
+### npm
+
+```shell
+npm install angular-loader
+```
+
+Add a `
+```
+
+Note that this package is not in CommonJS format, so doing `require('angular-loader')` will
+return `undefined`.
+
+### bower
+
+```shell
+bower install angular-loader
+```
+
+Add a `
+```
+
+## Documentation
+
+Documentation is available on the
+[AngularJS docs site](http://docs.angularjs.org/guide/bootstrap).
+
+## License
+
+The MIT License
+
+Copyright (c) 2010-2015 Google, Inc. http://angularjs.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-loader/angular-loader.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-loader/angular-loader.js
new file mode 100644
index 0000000000..3dd430cdbe
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-loader/angular-loader.js
@@ -0,0 +1,533 @@
+/**
+ * @license AngularJS v1.6.4
+ * (c) 2010-2017 Google, Inc. http://angularjs.org
+ * License: MIT
+ */
+
+(function() {'use strict';
+ function isFunction(value) {return typeof value === 'function';}
+ function isDefined(value) {return typeof value !== 'undefined';}
+ function isObject(value) {return value !== null && typeof value === 'object';}
+
+/* global toDebugString: true */
+
+function serializeObject(obj, maxDepth) {
+ 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)) {
+ obj = copy(obj, null, maxDepth);
+ }
+ return JSON.stringify(obj, function(key, val) {
+ val = toJsonReplacer(key, val);
+ if (isObject(val)) {
+
+ if (seen.indexOf(val) >= 0) return '...';
+
+ seen.push(val);
+ }
+ return val;
+ });
+}
+
+function toDebugString(obj, maxDepth) {
+ if (typeof obj === 'function') {
+ return obj.toString().replace(/ \{[\s\S]*$/, '');
+ } else if (isUndefined(obj)) {
+ return 'undefined';
+ } else if (typeof obj !== 'string') {
+ return serializeObject(obj, maxDepth);
+ }
+ return obj;
+}
+
+/**
+ * @description
+ *
+ * This object provides a utility for producing rich Error messages within
+ * Angular. It can be called as follows:
+ *
+ * var exampleMinErr = minErr('example');
+ * throw exampleMinErr('one', 'This {0} is {1}', foo, bar);
+ *
+ * The above creates an instance of minErr in the example namespace. The
+ * resulting error will have a namespaced error code of example.one. The
+ * resulting error will replace {0} with the value of foo, and {1} with the
+ * value of bar. The object is not restricted in the number of arguments it can
+ * take.
+ *
+ * If fewer arguments are specified than necessary for interpolation, the extra
+ * interpolation markers will be preserved in the final string.
+ *
+ * Since data will be parsed statically during a build step, some restrictions
+ * are applied with respect to how minErr instances are created and called.
+ * Instances should have names of the form namespaceMinErr for a minErr created
+ * using minErr('namespace') . Error codes, namespaces and template strings
+ * should all be static strings, not variables or general expressions.
+ *
+ * @param {string} module The namespace to use for the new minErr instance.
+ * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning
+ * error from returned function, for cases when a particular type of error is useful.
+ * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance
+ */
+
+function minErr(module, ErrorConstructor) {
+ ErrorConstructor = ErrorConstructor || Error;
+ return function() {
+ var code = arguments[0],
+ template = arguments[1],
+ message = '[' + (module ? module + ':' : '') + code + '] ',
+ templateArgs = sliceArgs(arguments, 2).map(function(arg) {
+ return toDebugString(arg, minErrConfig.objectMaxDepth);
+ }),
+ paramPrefix, i;
+
+ message += template.replace(/\{\d+\}/g, function(match) {
+ var index = +match.slice(1, -1);
+
+ if (index < templateArgs.length) {
+ return templateArgs[index];
+ }
+
+ return match;
+ });
+
+ message += '\nhttp://errors.angularjs.org/1.6.4/' +
+ (module ? module + '/' : '') + code;
+
+ for (i = 0, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') {
+ message += paramPrefix + 'p' + i + '=' + encodeURIComponent(templateArgs[i]);
+ }
+
+ return new ErrorConstructor(message);
+ };
+}
+
+/**
+ * @ngdoc type
+ * @name angular.Module
+ * @module ng
+ * @description
+ *
+ * Interface for configuring angular {@link angular.module modules}.
+ */
+
+function setupModuleLoader(window) {
+
+ var $injectorMinErr = minErr('$injector');
+ var ngMinErr = minErr('ng');
+
+ function ensure(obj, name, factory) {
+ return obj[name] || (obj[name] = factory());
+ }
+
+ var angular = ensure(window, 'angular', Object);
+
+ // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
+ angular.$$minErr = angular.$$minErr || minErr;
+
+ return ensure(angular, 'module', function() {
+ /** @type {Object.} */
+ var modules = {};
+
+ /**
+ * @ngdoc function
+ * @name angular.module
+ * @module ng
+ * @description
+ *
+ * The `angular.module` is a global place for creating, registering and retrieving Angular
+ * modules.
+ * All modules (angular core or 3rd party) that should be available to an application must be
+ * registered using this mechanism.
+ *
+ * Passing one argument retrieves an existing {@link angular.Module},
+ * whereas passing more than one argument creates a new {@link angular.Module}
+ *
+ *
+ * # Module
+ *
+ * A module is a collection of services, directives, controllers, filters, and configuration information.
+ * `angular.module` is used to configure the {@link auto.$injector $injector}.
+ *
+ * ```js
+ * // Create a new module
+ * var myModule = angular.module('myModule', []);
+ *
+ * // register a new service
+ * myModule.value('appName', 'MyCoolApp');
+ *
+ * // configure existing services inside initialization blocks.
+ * myModule.config(['$locationProvider', function($locationProvider) {
+ * // Configure existing providers
+ * $locationProvider.hashPrefix('!');
+ * }]);
+ * ```
+ *
+ * Then you can create an injector and load your modules like this:
+ *
+ * ```js
+ * var injector = angular.injector(['ng', 'myModule'])
+ * ```
+ *
+ * However it's more likely that you'll just use
+ * {@link ng.directive:ngApp ngApp} or
+ * {@link angular.bootstrap} to simplify this process for you.
+ *
+ * @param {!string} name The name of the module to create or retrieve.
+ * @param {!Array.=} requires If specified then new module is being created. If
+ * unspecified then the module is being retrieved for further configuration.
+ * @param {Function=} configFn Optional configuration function for the module. Same as
+ * {@link angular.Module#config Module#config()}.
+ * @returns {angular.Module} new module with the {@link angular.Module} api.
+ */
+ return function module(name, requires, configFn) {
+
+ var info = {};
+
+ var assertNotHasOwnProperty = function(name, context) {
+ if (name === 'hasOwnProperty') {
+ throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context);
+ }
+ };
+
+ assertNotHasOwnProperty(name, 'module');
+ if (requires && modules.hasOwnProperty(name)) {
+ modules[name] = null;
+ }
+ return ensure(modules, name, function() {
+ if (!requires) {
+ 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 ' +
+ 'specify the dependencies as the second argument.', name);
+ }
+
+ /** @type {!Array.>} */
+ var invokeQueue = [];
+
+ /** @type {!Array.} */
+ var configBlocks = [];
+
+ /** @type {!Array.} */
+ var runBlocks = [];
+
+ var config = invokeLater('$injector', 'invoke', 'push', configBlocks);
+
+ /** @type {angular.Module} */
+ var moduleInstance = {
+ // Private state
+ _invokeQueue: invokeQueue,
+ _configBlocks: configBlocks,
+ _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
+ * @name angular.Module#requires
+ * @module ng
+ *
+ * @description
+ * Holds the list of modules which the injector will load before the current module is
+ * loaded.
+ */
+ requires: requires,
+
+ /**
+ * @ngdoc property
+ * @name angular.Module#name
+ * @module ng
+ *
+ * @description
+ * Name of the module.
+ */
+ name: name,
+
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#provider
+ * @module ng
+ * @param {string} name service name
+ * @param {Function} providerType Construction function for creating new instance of the
+ * service.
+ * @description
+ * See {@link auto.$provide#provider $provide.provider()}.
+ */
+ provider: invokeLaterAndSetModuleName('$provide', 'provider'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#factory
+ * @module ng
+ * @param {string} name service name
+ * @param {Function} providerFunction Function for creating new instance of the service.
+ * @description
+ * See {@link auto.$provide#factory $provide.factory()}.
+ */
+ factory: invokeLaterAndSetModuleName('$provide', 'factory'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#service
+ * @module ng
+ * @param {string} name service name
+ * @param {Function} constructor A constructor function that will be instantiated.
+ * @description
+ * See {@link auto.$provide#service $provide.service()}.
+ */
+ service: invokeLaterAndSetModuleName('$provide', 'service'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#value
+ * @module ng
+ * @param {string} name service name
+ * @param {*} object Service instance object.
+ * @description
+ * See {@link auto.$provide#value $provide.value()}.
+ */
+ value: invokeLater('$provide', 'value'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#constant
+ * @module ng
+ * @param {string} name constant name
+ * @param {*} object Constant value.
+ * @description
+ * Because the constants are fixed, they get applied before other provide methods.
+ * See {@link auto.$provide#constant $provide.constant()}.
+ */
+ constant: invokeLater('$provide', 'constant', 'unshift'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#decorator
+ * @module ng
+ * @param {string} name The name of the service to decorate.
+ * @param {Function} decorFn This function will be invoked when the service needs to be
+ * instantiated and should return the decorated service instance.
+ * @description
+ * See {@link auto.$provide#decorator $provide.decorator()}.
+ */
+ decorator: invokeLaterAndSetModuleName('$provide', 'decorator', configBlocks),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#animation
+ * @module ng
+ * @param {string} name animation name
+ * @param {Function} animationFactory Factory function for creating new instance of an
+ * animation.
+ * @description
+ *
+ * **NOTE**: animations take effect only if the **ngAnimate** module is loaded.
+ *
+ *
+ * Defines an animation hook that can be later used with
+ * {@link $animate $animate} service and directives that use this service.
+ *
+ * ```js
+ * module.animation('.animation-name', function($inject1, $inject2) {
+ * return {
+ * eventName : function(element, done) {
+ * //code to run the animation
+ * //once complete, then run done()
+ * return function cancellationFunction(element) {
+ * //code to cancel the animation
+ * }
+ * }
+ * }
+ * })
+ * ```
+ *
+ * See {@link ng.$animateProvider#register $animateProvider.register()} and
+ * {@link ngAnimate ngAnimate module} for more information.
+ */
+ animation: invokeLaterAndSetModuleName('$animateProvider', 'register'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#filter
+ * @module ng
+ * @param {string} name Filter name - this must be a valid angular expression identifier
+ * @param {Function} filterFactory Factory function for creating new instance of filter.
+ * @description
+ * See {@link ng.$filterProvider#register $filterProvider.register()}.
+ *
+ *
+ * **Note:** Filter names must be valid angular {@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
+ * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
+ * (`myapp_subsection_filterx`).
+ *
+ */
+ filter: invokeLaterAndSetModuleName('$filterProvider', 'register'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#controller
+ * @module ng
+ * @param {string|Object} name Controller name, or an object map of controllers where the
+ * keys are the names and the values are the constructors.
+ * @param {Function} constructor Controller constructor function.
+ * @description
+ * See {@link ng.$controllerProvider#register $controllerProvider.register()}.
+ */
+ controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#directive
+ * @module ng
+ * @param {string|Object} name Directive name, or an object map of directives where the
+ * keys are the names and the values are the factories.
+ * @param {Function} directiveFactory Factory function for creating new instance of
+ * directives.
+ * @description
+ * See {@link ng.$compileProvider#directive $compileProvider.directive()}.
+ */
+ directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#component
+ * @module ng
+ * @param {string} name Name of the component in camel-case (i.e. myComp which will match as my-comp)
+ * @param {Object} options Component definition object (a simplified
+ * {@link ng.$compile#directive-definition-object directive definition object})
+ *
+ * @description
+ * See {@link ng.$compileProvider#component $compileProvider.component()}.
+ */
+ component: invokeLaterAndSetModuleName('$compileProvider', 'component'),
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#config
+ * @module ng
+ * @param {Function} configFn Execute this function on module load. Useful for service
+ * configuration.
+ * @description
+ * Use this method to register work which needs to be performed on module loading.
+ * For more about how to configure services, see
+ * {@link providers#provider-recipe Provider Recipe}.
+ */
+ config: config,
+
+ /**
+ * @ngdoc method
+ * @name angular.Module#run
+ * @module ng
+ * @param {Function} initializationFn Execute this function after injector creation.
+ * Useful for application initialization.
+ * @description
+ * Use this method to register work which should be performed when the injector is done
+ * loading all modules.
+ */
+ run: function(block) {
+ runBlocks.push(block);
+ return this;
+ }
+ };
+
+ if (configFn) {
+ config(configFn);
+ }
+
+ return moduleInstance;
+
+ /**
+ * @param {string} provider
+ * @param {string} method
+ * @param {String=} insertMethod
+ * @returns {angular.Module}
+ */
+ function invokeLater(provider, method, insertMethod, queue) {
+ if (!queue) queue = invokeQueue;
+ return function() {
+ queue[insertMethod || 'push']([provider, method, arguments]);
+ return moduleInstance;
+ };
+ }
+
+ /**
+ * @param {string} provider
+ * @param {string} method
+ * @returns {angular.Module}
+ */
+ function invokeLaterAndSetModuleName(provider, method, queue) {
+ if (!queue) queue = invokeQueue;
+ return function(recipeName, factoryFunction) {
+ if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name;
+ queue.push([provider, method, arguments]);
+ return moduleInstance;
+ };
+ }
+ });
+ };
+ });
+
+}
+
+setupModuleLoader(window);
+})(window);
+
+/**
+ * Closure compiler type information
+ *
+ * @typedef { {
+ * requires: !Array.,
+ * invokeQueue: !Array.>,
+ *
+ * service: function(string, Function):angular.Module,
+ * factory: function(string, Function):angular.Module,
+ * value: function(string, *):angular.Module,
+ *
+ * filter: function(string, Function):angular.Module,
+ *
+ * init: function(Function):angular.Module
+ * } }
+ */
+angular.Module;
+
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-loader/angular-loader.min.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-loader/angular-loader.min.js
new file mode 100644
index 0000000000..73cfdea232
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-loader/angular-loader.min.js
@@ -0,0 +1,10 @@
+/*
+ AngularJS v1.6.4
+ (c) 2010-2017 Google, Inc. http://angularjs.org
+ License: MIT
+*/
+(function(){'use strict';function g(a,f){f=f||Error;return function(){var d=arguments[0],e;e="["+(a?a+":":"")+d+"] http://errors.angularjs.org/1.6.4/"+(a?a+"/":"")+d;for(d=1;d",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/angular/angular.js/issues"
+ },
+ "homepage": "http://angularjs.org",
+ "jspm": {
+ "shim": {
+ "angular-loader": {
+ "deps": ["angular"]
+ }
+ }
+ }
+}
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-resource/LICENSE.md b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-resource/LICENSE.md
new file mode 100644
index 0000000000..2c395eef1b
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-resource/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Angular
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-resource/README.md b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-resource/README.md
new file mode 100644
index 0000000000..f3bd119ce2
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-resource/README.md
@@ -0,0 +1,68 @@
+# packaged angular-resource
+
+This repo is for distribution on `npm` and `bower`. The source for this module is in the
+[main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngResource).
+Please file issues and pull requests against that repo.
+
+## Install
+
+You can install this package either with `npm` or with `bower`.
+
+### npm
+
+```shell
+npm install angular-resource
+```
+
+Then add `ngResource` as a dependency for your app:
+
+```javascript
+angular.module('myApp', [require('angular-resource')]);
+```
+
+### bower
+
+```shell
+bower install angular-resource
+```
+
+Add a `
+```
+
+Then add `ngResource` as a dependency for your app:
+
+```javascript
+angular.module('myApp', ['ngResource']);
+```
+
+## Documentation
+
+Documentation is available on the
+[AngularJS docs site](http://docs.angularjs.org/api/ngResource).
+
+## License
+
+The MIT License
+
+Copyright (c) 2010-2015 Google, Inc. http://angularjs.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-resource/angular-resource.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-resource/angular-resource.js
new file mode 100644
index 0000000000..41a6697a6e
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-resource/angular-resource.js
@@ -0,0 +1,858 @@
+/**
+ * @license AngularJS v1.6.4
+ * (c) 2010-2017 Google, Inc. http://angularjs.org
+ * License: MIT
+ */
+(function(window, angular) {'use strict';
+
+var $resourceMinErr = angular.$$minErr('$resource');
+
+// Helper functions and regex to lookup a dotted path on an object
+// stopping at undefined/null. The path must be composed of ASCII
+// identifiers (just like $parse)
+var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/;
+
+function isValidDottedPath(path) {
+ return (path != null && path !== '' && path !== 'hasOwnProperty' &&
+ MEMBER_NAME_REGEX.test('.' + path));
+}
+
+function lookupDottedPath(obj, path) {
+ if (!isValidDottedPath(path)) {
+ throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path);
+ }
+ var keys = path.split('.');
+ for (var i = 0, ii = keys.length; i < ii && angular.isDefined(obj); i++) {
+ var key = keys[i];
+ obj = (obj !== null) ? obj[key] : undefined;
+ }
+ return obj;
+}
+
+/**
+ * Create a shallow copy of an object and clear other fields from the destination
+ */
+function shallowClearAndCopy(src, dst) {
+ dst = dst || {};
+
+ angular.forEach(dst, function(value, key) {
+ delete dst[key];
+ });
+
+ for (var key in src) {
+ if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
+ dst[key] = src[key];
+ }
+ }
+
+ return dst;
+}
+
+/**
+ * @ngdoc module
+ * @name ngResource
+ * @description
+ *
+ * # ngResource
+ *
+ * The `ngResource` module provides interaction support with RESTful services
+ * via the $resource service.
+ *
+ *
+ *
+ *
+ * See {@link ngResource.$resourceProvider} and {@link ngResource.$resource} for usage.
+ */
+
+/**
+ * @ngdoc provider
+ * @name $resourceProvider
+ *
+ * @description
+ *
+ * Use `$resourceProvider` to change the default behavior of the {@link ngResource.$resource}
+ * service.
+ *
+ * ## Dependencies
+ * Requires the {@link ngResource } module to be installed.
+ *
+ */
+
+/**
+ * @ngdoc service
+ * @name $resource
+ * @requires $http
+ * @requires ng.$log
+ * @requires $q
+ * @requires ng.$timeout
+ *
+ * @description
+ * A factory which creates a resource object that lets you interact with
+ * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
+ *
+ * The returned resource object has action methods which provide high-level behaviors without
+ * the need to interact with the low level {@link ng.$http $http} service.
+ *
+ * Requires the {@link ngResource `ngResource`} module to be installed.
+ *
+ * By default, trailing slashes will be stripped from the calculated URLs,
+ * which can pose problems with server backends that do not expect that
+ * behavior. This can be disabled by configuring the `$resourceProvider` like
+ * this:
+ *
+ * ```js
+ app.config(['$resourceProvider', function($resourceProvider) {
+ // Don't strip trailing slashes from calculated URLs
+ $resourceProvider.defaults.stripTrailingSlashes = false;
+ }]);
+ * ```
+ *
+ * @param {string} url A parameterized URL template with parameters prefixed by `:` as in
+ * `/user/:username`. If you are using a URL with a port number (e.g.
+ * `http://example.com:8080/api`), it will be respected.
+ *
+ * If you are using a url with a suffix, just add the suffix, like this:
+ * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')`
+ * or even `$resource('http://example.com/resource/:resource_id.:format')`
+ * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
+ * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you
+ * can escape it with `/\.`.
+ *
+ * @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
+ * a param value needs to be obtained for a request (unless the param was overridden). The 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
+ * excess keys are appended to the url search query after the `?`.
+ *
+ * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
+ * URL `/path/greet?salutation=Hello`.
+ *
+ * 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 actions
+ * with a request body).
+ * For example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of
+ * `someParam` will be `data.someProp`.
+ * 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)
+ *
+ * @param {Object.=} actions Hash with declaration of custom actions that will be available
+ * in addition to the default set of resource actions (see below). If a custom action has the same
+ * key as a default action (e.g. `save`), then the default action will be *overwritten*, and not
+ * extended.
+ *
+ * The declaration should be created in the format of {@link ng.$http#usage $http.config}:
+ *
+ * {action1: {method:?, params:?, isArray:?, headers:?, ...},
+ * action2: {method:?, params:?, isArray:?, headers:?, ...},
+ * ...}
+ *
+ * Where:
+ *
+ * - **`action`** – {string} – The name of action. This name becomes the name of the method on
+ * your resource object.
+ * - **`method`** – {string} – Case insensitive HTTP method (e.g. `GET`, `POST`, `PUT`,
+ * `DELETE`, `JSONP`, etc).
+ * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of
+ * 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
+ * current data value as an argument.
+ * - **`url`** – {string} – action specific `url` override. The url templating is supported just
+ * like for the resource-level urls.
+ * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array,
+ * see `returns` section.
+ * - **`transformRequest`** –
+ * `{function(data, headersGetter)|Array.}` –
+ * 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.
+ * By default, transformRequest will contain one function that checks if the request data is
+ * an object and serializes it using `angular.toJson`. To prevent this behavior, set
+ * `transformRequest` to an empty array: `transformRequest: []`
+ * - **`transformResponse`** –
+ * `{function(data, headersGetter, status)|Array.}` –
+ * transform function or an array of such functions. The transform function takes the http
+ * 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
+ * like a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior,
+ * set `transformResponse` to an empty array: `transformResponse: []`
+ * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
+ * GET request, otherwise if a cache instance built with
+ * {@link ng.$cacheFactory $cacheFactory} is supplied, this cache will be used for
+ * caching.
+ * - **`timeout`** – `{number}` – timeout in milliseconds.
+ * **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.
+ * 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
+ * will be cancelled (if not already completed) by calling `$cancelRequest()` on the call's
+ * return value. Calling `$cancelRequest()` for a non-cancellable or an already
+ * completed/cancelled request will have no effect.
+ * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the
+ * XHR object. See
+ * [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5)
+ * for more information.
+ * - **`responseType`** - `{string}` - see
+ * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType).
+ * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
+ * `response` and `responseError`. Both `response` and `responseError` interceptors get called
+ * with `http response` object. See {@link ng.$http $http interceptors}.
+ * - **`hasBody`** - `{boolean}` - allows to specify if a request body should be included or not.
+ * If not specified only POST, PUT and PATCH requests will have a body.
+ *
+ * @param {Object} options Hash with custom settings that should extend the
+ * default `$resourceProvider` behavior. The supported options are:
+ *
+ * - **`stripTrailingSlashes`** – {boolean} – If true then the trailing
+ * slashes from any calculated URL will be stripped. (Defaults to true.)
+ * - **`cancellable`** – {boolean} – If true, the request made by a "non-instance" call will be
+ * cancelled (if not already completed) by calling `$cancelRequest()` on the call's return value.
+ * This can be overwritten per action. (Defaults to false.)
+ *
+ * @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:
+ * ```js
+ * { 'get': {method:'GET'},
+ * 'save': {method:'POST'},
+ * 'query': {method:'GET', isArray:true},
+ * 'remove': {method:'DELETE'},
+ * 'delete': {method:'DELETE'} };
+ * ```
+ *
+ * Calling these methods invoke an {@link ng.$http} with the specified http method,
+ * destination and parameters. When the data is returned from the server then the object is an
+ * instance of the resource class. The actions `save`, `remove` and `delete` are available on it
+ * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
+ * read, update, delete) on server-side data like this:
+ * ```js
+ * var User = $resource('/user/:userId', {userId:'@id'});
+ * var user = User.get({userId:123}, function() {
+ * user.abc = true;
+ * user.$save();
+ * });
+ * ```
+ *
+ * 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
+ * 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
+ * object results in no rendering, once the data arrives from the server then the object is
+ * populated with the data and the view automatically re-renders itself showing the new data. This
+ * means that in most cases one never has to write a callback function for the action methods.
+ *
+ * The action methods on the class object or instance object can be invoked with the following
+ * parameters:
+ *
+ * - "class" actions without a body: `Resource.action([parameters], [success], [error])`
+ * - "class" actions with a body: `Resource.action([parameters], postData, [success], [error])`
+ * - instance actions: `instance.$action([parameters], [success], [error])`
+ *
+ *
+ * When calling instance methods, the instance itself is used as the request body (if the action
+ * should have a body). By default, only actions using `POST`, `PUT` or `PATCH` have request
+ * 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).
+ *
+ *
+ * Success callback is called with (value (Object|Array), responseHeaders (Function),
+ * status (number), statusText (string)) arguments, where the value is the populated resource
+ * instance or collection object. The error callback is called with (httpResponse) argument.
+ *
+ * Class actions return empty instance (with additional properties below).
+ * Instance actions return promise of the action.
+ *
+ * The Resource instances and collections have these additional properties:
+ *
+ * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this
+ * instance or collection.
+ *
+ * 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
+ * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view
+ * rendering until the resource(s) are loaded.
+ *
+ * On failure, the promise is rejected with the {@link ng.$http http response} object, without
+ * the `resource` property.
+ *
+ * If an interceptor object was provided, the promise will instead be resolved with the value
+ * returned by the interceptor.
+ *
+ * - `$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
+ * data-binding.
+ *
+ * The Resource instances and collections have these additional methods:
+ *
+ * - `$cancelRequest`: If there is a cancellable, pending request related to the instance or
+ * collection, calling this method will abort the request.
+ *
+ * The Resource instances have these additional methods:
+ *
+ * - `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
+ * without attaching Angular-specific fields. Notice that `JSON.stringify` (and
+ * `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%28%29_behavior)).
+ *
+ * @example
+ *
+ * # Credit card resource
+ *
+ * ```js
+ // Define CreditCard class
+ var CreditCard = $resource('/user/:userId/card/:cardId',
+ {userId:123, cardId:'@id'}, {
+ charge: {method:'POST', params:{charge:true}}
+ });
+
+ // We can retrieve a collection from the server
+ var cards = CreditCard.query(function() {
+ // GET: /user/123/card
+ // server returns: [ {id:456, number:'1234', name:'Smith'} ];
+
+ 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.
+ card.$charge({amount:9.99});
+ // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
+ });
+
+ // we can create an instance as well
+ var newCard = new CreditCard({number:'0123'});
+ newCard.name = "Mike Smith";
+ newCard.$save();
+ // POST: /user/123/card {number:'0123', name:'Mike Smith'}
+ // server returns: {id:789, number:'0123', name: 'Mike Smith'};
+ expect(newCard.id).toEqual(789);
+ * ```
+ *
+ * The object returned from this function execution is a resource "class" which has "static" method
+ * for each action in the definition.
+ *
+ * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and
+ * `headers`.
+ *
+ * @example
+ *
+ * # User resource
+ *
+ * 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
+ * operations (create, read, update, delete) on server-side data.
+
+ ```js
+ var User = $resource('/user/:userId', {userId:'@id'});
+ User.get({userId:123}, function(user) {
+ user.abc = true;
+ user.$save();
+ });
+ ```
+ *
+ * It's worth noting that the success callback for `get`, `query` and other methods gets passed
+ * in the response that came from the server as well as $http header getter function, so one
+ * could rewrite the above example and get access to http headers as:
+ *
+ ```js
+ var User = $resource('/user/:userId', {userId:'@id'});
+ User.get({userId:123}, function(user, getResponseHeaders){
+ user.abc = true;
+ user.$save(function(user, putResponseHeaders) {
+ //user => saved user object
+ //putResponseHeaders => $http header getter
+ });
+ });
+ ```
+ *
+ * You can also access the raw `$http` promise via the `$promise` property on the object returned
+ *
+ ```
+ var User = $resource('/user/:userId', {userId:'@id'});
+ User.get({userId:123})
+ .$promise.then(function(user) {
+ $scope.user = user;
+ });
+ ```
+ *
+ * @example
+ *
+ * # Creating a custom 'PUT' request
+ *
+ * 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
+ * to an instance or collection (as long as it is a result of a "non-instance" call):
+ *
+ ```js
+ // ...defining the `Hotel` resource...
+ var Hotel = $resource('/api/hotel/:id', {id: '@id'}, {
+ // Let's make the `query()` method cancellable
+ query: {method: 'get', isArray: true, cancellable: true}
+ });
+
+ // ...somewhere in the PlanVacationController...
+ ...
+ this.onDestinationChanged = function onDestinationChanged(destination) {
+ // We don't care about any pending request for hotels
+ // in a different destination any more
+ this.availableHotels.$cancelRequest();
+
+ // Let's query for hotels in ''
+ // (calls: /api/hotel?location=)
+ this.availableHotels = Hotel.query({location: destination});
+ };
+ ```
+ *
+ */
+angular.module('ngResource', ['ng']).
+ info({ angularVersion: '1.6.4' }).
+ provider('$resource', function ResourceProvider() {
+ var PROTOCOL_AND_IPV6_REGEX = /^https?:\/\/\[[^\]]*][^/]*/;
+
+ var provider = this;
+
+ /**
+ * @ngdoc property
+ * @name $resourceProvider#defaults
+ * @description
+ * Object containing default options used when creating `$resource` instances.
+ *
+ * The default values satisfy a wide range of usecases, but you may choose to overwrite any of
+ * them to further customize your instances. The available properties are:
+ *
+ * - **stripTrailingSlashes** – `{boolean}` – If true, then the trailing slashes from any
+ * calculated URL will be stripped.
+ * (Defaults to true.)
+ * - **cancellable** – `{boolean}` – If true, the request made by a "non-instance" call will be
+ * cancelled (if not already completed) by calling `$cancelRequest()` on the call's return
+ * value. For more details, see {@link ngResource.$resource}. This can be overwritten per
+ * resource class or action.
+ * (Defaults to false.)
+ * - **actions** - `{Object.}` - A hash with default actions declarations. Actions are
+ * high-level methods corresponding to RESTful actions/methods on resources. An action may
+ * specify what HTTP method to use, what URL to hit, if the return value will be a single
+ * object or a collection (array) of objects etc. For more details, see
+ * {@link ngResource.$resource}. The actions can also be enhanced or overwritten per resource
+ * class.
+ * The default actions are:
+ * ```js
+ * {
+ * get: {method: 'GET'},
+ * save: {method: 'POST'},
+ * query: {method: 'GET', isArray: true},
+ * remove: {method: 'DELETE'},
+ * delete: {method: 'DELETE'}
+ * }
+ * ```
+ *
+ * #### Example
+ *
+ * For example, you can specify a new `update` action that uses the `PUT` HTTP verb:
+ *
+ * ```js
+ * angular.
+ * module('myApp').
+ * config(['$resourceProvider', function ($resourceProvider) {
+ * $resourceProvider.defaults.actions.update = {
+ * method: 'PUT'
+ * };
+ * });
+ * ```
+ *
+ * Or you can even overwrite the whole `actions` list and specify your own:
+ *
+ * ```js
+ * angular.
+ * module('myApp').
+ * config(['$resourceProvider', function ($resourceProvider) {
+ * $resourceProvider.defaults.actions = {
+ * create: {method: 'POST'},
+ * get: {method: 'GET'},
+ * getAll: {method: 'GET', isArray:true},
+ * update: {method: 'PUT'},
+ * delete: {method: 'DELETE'}
+ * };
+ * });
+ * ```
+ *
+ */
+ this.defaults = {
+ // Strip slashes by default
+ stripTrailingSlashes: true,
+
+ // Make non-instance requests cancellable (via `$cancelRequest()`)
+ cancellable: false,
+
+ // Default actions configuration
+ actions: {
+ 'get': {method: 'GET'},
+ 'save': {method: 'POST'},
+ 'query': {method: 'GET', isArray: true},
+ 'remove': {method: 'DELETE'},
+ 'delete': {method: 'DELETE'}
+ }
+ };
+
+ this.$get = ['$http', '$log', '$q', '$timeout', function($http, $log, $q, $timeout) {
+
+ var noop = angular.noop,
+ forEach = angular.forEach,
+ extend = angular.extend,
+ copy = angular.copy,
+ isArray = angular.isArray,
+ isDefined = angular.isDefined,
+ isFunction = angular.isFunction,
+ isNumber = angular.isNumber,
+ encodeUriQuery = angular.$$encodeUriQuery,
+ encodeUriSegment = angular.$$encodeUriSegment;
+
+ function Route(template, defaults) {
+ this.template = template;
+ this.defaults = extend({}, provider.defaults, defaults);
+ this.urlParams = {};
+ }
+
+ Route.prototype = {
+ setUrlParams: function(config, params, actionUrl) {
+ var self = this,
+ url = actionUrl || self.template,
+ val,
+ encodedVal,
+ protocolAndIpv6 = '';
+
+ var urlParams = self.urlParams = Object.create(null);
+ forEach(url.split(/\W/), function(param) {
+ if (param === 'hasOwnProperty') {
+ throw $resourceMinErr('badname', 'hasOwnProperty is not a valid parameter name.');
+ }
+ if (!(new RegExp('^\\d+$').test(param)) && param &&
+ (new RegExp('(^|[^\\\\]):' + param + '(\\W|$)').test(url))) {
+ urlParams[param] = {
+ isQueryParamValue: (new RegExp('\\?.*=:' + param + '(?:\\W|$)')).test(url)
+ };
+ }
+ });
+ url = url.replace(/\\:/g, ':');
+ url = url.replace(PROTOCOL_AND_IPV6_REGEX, function(match) {
+ protocolAndIpv6 = match;
+ return '';
+ });
+
+ params = params || {};
+ forEach(self.urlParams, function(paramInfo, urlParam) {
+ val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
+ if (isDefined(val) && val !== null) {
+ if (paramInfo.isQueryParamValue) {
+ encodedVal = encodeUriQuery(val, true);
+ } else {
+ encodedVal = encodeUriSegment(val);
+ }
+ url = url.replace(new RegExp(':' + urlParam + '(\\W|$)', 'g'), function(match, p1) {
+ return encodedVal + p1;
+ });
+ } else {
+ url = url.replace(new RegExp('(/?):' + urlParam + '(\\W|$)', 'g'), function(match,
+ leadingSlashes, tail) {
+ if (tail.charAt(0) === '/') {
+ return tail;
+ } else {
+ return leadingSlashes + tail;
+ }
+ });
+ }
+ });
+
+ // strip trailing slashes and set the url (unless this behavior is specifically disabled)
+ if (self.defaults.stripTrailingSlashes) {
+ url = url.replace(/\/+$/, '') || '/';
+ }
+
+ // 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`.
+ url = url.replace(/\/\.(?=\w+($|\?))/, '.');
+ // Replace escaped `/\.` with `/.`.
+ // (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
+ forEach(params, function(value, key) {
+ if (!self.urlParams[key]) {
+ config.params = config.params || {};
+ config.params[key] = value;
+ }
+ });
+ }
+ };
+
+
+ function resourceFactory(url, paramDefaults, actions, options) {
+ var route = new Route(url, options);
+
+ actions = extend({}, provider.defaults.actions, actions);
+
+ function extractParams(data, actionParams) {
+ var ids = {};
+ actionParams = extend({}, paramDefaults, actionParams);
+ forEach(actionParams, function(value, key) {
+ if (isFunction(value)) { value = value(data); }
+ ids[key] = value && value.charAt && value.charAt(0) === '@' ?
+ lookupDottedPath(data, value.substr(1)) : value;
+ });
+ return ids;
+ }
+
+ function defaultResponseInterceptor(response) {
+ return response.resource;
+ }
+
+ function Resource(value) {
+ shallowClearAndCopy(value || {}, this);
+ }
+
+ Resource.prototype.toJSON = function() {
+ var data = extend({}, this);
+ delete data.$promise;
+ delete data.$resolved;
+ delete data.$cancelRequest;
+ return data;
+ };
+
+ forEach(actions, function(action, name) {
+ var hasBody = action.hasBody === true || (action.hasBody !== false && /^(POST|PUT|PATCH)$/i.test(action.method));
+ var numericTimeout = action.timeout;
+ var cancellable = isDefined(action.cancellable) ?
+ action.cancellable : route.defaults.cancellable;
+
+ if (numericTimeout && !isNumber(numericTimeout)) {
+ $log.debug('ngResource:\n' +
+ ' Only numeric values are allowed as `timeout`.\n' +
+ ' Promises are 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.');
+ delete action.timeout;
+ numericTimeout = null;
+ }
+
+ Resource[name] = function(a1, a2, a3, a4) {
+ var params = {}, data, success, error;
+
+ switch (arguments.length) {
+ case 4:
+ error = a4;
+ success = a3;
+ // falls through
+ case 3:
+ case 2:
+ if (isFunction(a2)) {
+ if (isFunction(a1)) {
+ success = a1;
+ error = a2;
+ break;
+ }
+
+ success = a2;
+ error = a3;
+ // falls through
+ } else {
+ params = a1;
+ data = a2;
+ success = a3;
+ break;
+ }
+ // falls through
+ case 1:
+ if (isFunction(a1)) success = a1;
+ else if (hasBody) data = a1;
+ else params = a1;
+ break;
+ case 0: break;
+ default:
+ throw $resourceMinErr('badargs',
+ 'Expected up to 4 arguments [params, data, success, error], got {0} arguments',
+ arguments.length);
+ }
+
+ var isInstanceCall = this instanceof Resource;
+ var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data));
+ var httpConfig = {};
+ var responseInterceptor = action.interceptor && action.interceptor.response ||
+ defaultResponseInterceptor;
+ var responseErrorInterceptor = action.interceptor && action.interceptor.responseError ||
+ undefined;
+ var hasError = !!error;
+ var hasResponseErrorInterceptor = !!responseErrorInterceptor;
+ var timeoutDeferred;
+ var numericTimeoutPromise;
+
+ forEach(action, function(value, key) {
+ switch (key) {
+ default:
+ httpConfig[key] = copy(value);
+ break;
+ case 'params':
+ case 'isArray':
+ case 'interceptor':
+ case 'cancellable':
+ break;
+ }
+ });
+
+ if (!isInstanceCall && cancellable) {
+ timeoutDeferred = $q.defer();
+ httpConfig.timeout = timeoutDeferred.promise;
+
+ if (numericTimeout) {
+ numericTimeoutPromise = $timeout(timeoutDeferred.resolve, numericTimeout);
+ }
+ }
+
+ if (hasBody) httpConfig.data = data;
+ route.setUrlParams(httpConfig,
+ extend({}, extractParams(data, action.params || {}), params),
+ action.url);
+
+ var promise = $http(httpConfig).then(function(response) {
+ var data = response.data;
+
+ if (data) {
+ // Need to convert action.isArray to boolean in case it is undefined
+ if (isArray(data) !== (!!action.isArray)) {
+ throw $resourceMinErr('badcfg',
+ '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',
+ isArray(data) ? 'array' : 'object', httpConfig.method, httpConfig.url);
+ }
+ if (action.isArray) {
+ value.length = 0;
+ forEach(data, function(item) {
+ if (typeof item === 'object') {
+ value.push(new Resource(item));
+ } else {
+ // Valid JSON values may be string literals, and these should not be converted
+ // into objects. These items will not have access to the Resource prototype
+ // methods, but unfortunately there
+ value.push(item);
+ }
+ });
+ } else {
+ var promise = value.$promise; // Save the promise
+ shallowClearAndCopy(data, value);
+ value.$promise = promise; // Restore the promise
+ }
+ }
+ response.resource = value;
+
+ return response;
+ });
+
+ promise = promise['finally'](function() {
+ value.$resolved = true;
+ if (!isInstanceCall && cancellable) {
+ value.$cancelRequest = noop;
+ $timeout.cancel(numericTimeoutPromise);
+ timeoutDeferred = numericTimeoutPromise = httpConfig.timeout = null;
+ }
+ });
+
+ promise = promise.then(
+ function(response) {
+ var value = responseInterceptor(response);
+ (success || noop)(value, response.headers, response.status, response.statusText);
+ return value;
+ },
+ (hasError || hasResponseErrorInterceptor) ?
+ function(response) {
+ if (hasError && !hasResponseErrorInterceptor) {
+ // Avoid `Possibly Unhandled Rejection` error,
+ // but still fulfill the returned promise with a rejection
+ promise.catch(noop);
+ }
+ if (hasError) error(response);
+ return hasResponseErrorInterceptor ?
+ responseErrorInterceptor(response) :
+ $q.reject(response);
+ } :
+ undefined);
+
+ if (!isInstanceCall) {
+ // we are creating instance / collection
+ // - set the initial promise
+ // - return the instance / collection
+ value.$promise = promise;
+ value.$resolved = false;
+ if (cancellable) value.$cancelRequest = cancelRequest;
+
+ return value;
+ }
+
+ // instance call
+ return promise;
+
+ function cancelRequest(value) {
+ promise.catch(noop);
+ timeoutDeferred.resolve(value);
+ }
+ };
+
+
+ Resource.prototype['$' + name] = function(params, success, error) {
+ if (isFunction(params)) {
+ error = success; success = params; params = {};
+ }
+ var result = Resource[name].call(this, params, this, success, error);
+ return result.$promise || result;
+ };
+ });
+
+ Resource.bind = function(additionalParamDefaults) {
+ var extendedParamDefaults = extend({}, paramDefaults, additionalParamDefaults);
+ return resourceFactory(url, extendedParamDefaults, actions, options);
+ };
+
+ return Resource;
+ }
+
+ return resourceFactory;
+ }];
+ });
+
+
+})(window, window.angular);
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-resource/angular-resource.min.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-resource/angular-resource.min.js
new file mode 100644
index 0000000000..9a6687d53b
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-resource/angular-resource.min.js
@@ -0,0 +1,15 @@
+/*
+ AngularJS v1.6.4
+ (c) 2010-2017 Google, Inc. http://angularjs.org
+ License: MIT
+*/
+(function(W,b){'use strict';function K(q,g){g=g||{};b.forEach(g,function(b,h){delete g[h]});for(var h in q)!q.hasOwnProperty(h)||"$"===h.charAt(0)&&"$"===h.charAt(1)||(g[h]=q[h]);return g}var B=b.$$minErr("$resource"),Q=/^(\.[a-zA-Z_$@][0-9a-zA-Z_$@]*)+$/;b.module("ngResource",["ng"]).info({angularVersion:"1.6.4"}).provider("$resource",function(){var q=/^https?:\/\/\[[^\]]*][^/]*/,g=this;this.defaults={stripTrailingSlashes:!0,cancellable:!1,actions:{get:{method:"GET"},save:{method:"POST"},query:{method:"GET",
+isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}}};this.$get=["$http","$log","$q","$timeout",function(h,P,L,M){function C(b,e){this.template=b;this.defaults=p({},g.defaults,e);this.urlParams={}}function x(D,e,u,m){function c(a,d){var c={};d=p({},e,d);t(d,function(d,l){y(d)&&(d=d(a));var f;if(d&&d.charAt&&"@"===d.charAt(0)){f=a;var k=d.substr(1);if(null==k||""===k||"hasOwnProperty"===k||!Q.test("."+k))throw B("badmember",k);for(var k=k.split("."),e=0,g=k.length;e",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/angular/angular.js/issues"
+ },
+ "homepage": "http://angularjs.org",
+ "jspm": {
+ "shim": {
+ "angular-resource": {
+ "deps": ["angular"]
+ }
+ }
+ }
+}
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-route/LICENSE.md b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-route/LICENSE.md
new file mode 100644
index 0000000000..2c395eef1b
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-route/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Angular
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-route/README.md b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-route/README.md
new file mode 100644
index 0000000000..2cd4f9091d
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-route/README.md
@@ -0,0 +1,68 @@
+# packaged angular-route
+
+This repo is for distribution on `npm` and `bower`. The source for this module is in the
+[main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngRoute).
+Please file issues and pull requests against that repo.
+
+## Install
+
+You can install this package either with `npm` or with `bower`.
+
+### npm
+
+```shell
+npm install angular-route
+```
+
+Then add `ngRoute` as a dependency for your app:
+
+```javascript
+angular.module('myApp', [require('angular-route')]);
+```
+
+### bower
+
+```shell
+bower install angular-route
+```
+
+Add a `
+```
+
+Then add `ngRoute` as a dependency for your app:
+
+```javascript
+angular.module('myApp', ['ngRoute']);
+```
+
+## Documentation
+
+Documentation is available on the
+[AngularJS docs site](http://docs.angularjs.org/api/ngRoute).
+
+## License
+
+The MIT License
+
+Copyright (c) 2010-2015 Google, Inc. http://angularjs.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-route/angular-route.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-route/angular-route.js
new file mode 100644
index 0000000000..0fc3404b8f
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-route/angular-route.js
@@ -0,0 +1,1229 @@
+/**
+ * @license AngularJS v1.6.4
+ * (c) 2010-2017 Google, Inc. http://angularjs.org
+ * License: MIT
+ */
+(function(window, angular) {'use strict';
+
+/* global shallowCopy: true */
+
+/**
+ * Creates a shallow copy of an object, an array or a primitive.
+ *
+ * Assumes that there are no proto properties for objects.
+ */
+function shallowCopy(src, dst) {
+ if (isArray(src)) {
+ dst = dst || [];
+
+ for (var i = 0, ii = src.length; i < ii; i++) {
+ dst[i] = src[i];
+ }
+ } else if (isObject(src)) {
+ dst = dst || {};
+
+ for (var key in src) {
+ if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) {
+ dst[key] = src[key];
+ }
+ }
+ }
+
+ return dst || src;
+}
+
+/* global shallowCopy: false */
+
+// `isArray` and `isObject` are necessary for `shallowCopy()` (included via `src/shallowCopy.js`).
+// They are initialized inside the `$RouteProvider`, to ensure `window.angular` is available.
+var isArray;
+var isObject;
+var isDefined;
+var noop;
+
+/**
+ * @ngdoc module
+ * @name ngRoute
+ * @description
+ *
+ * # ngRoute
+ *
+ * The `ngRoute` module provides routing and deeplinking services and directives for angular apps.
+ *
+ * ## Example
+ * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`.
+ *
+ *
+ *
+ */
+/* global -ngRouteModule */
+var ngRouteModule = angular.
+ module('ngRoute', []).
+ info({ angularVersion: '1.6.4' }).
+ 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
+ * @name $routeProvider
+ * @this
+ *
+ * @description
+ *
+ * Used for configuring routes.
+ *
+ * ## Example
+ * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`.
+ *
+ * ## Dependencies
+ * Requires the {@link ngRoute `ngRoute`} module to be installed.
+ */
+function $RouteProvider() {
+ isArray = angular.isArray;
+ isObject = angular.isObject;
+ isDefined = angular.isDefined;
+ noop = angular.noop;
+
+ function inherit(parent, extra) {
+ return angular.extend(Object.create(parent), extra);
+ }
+
+ var routes = {};
+
+ /**
+ * @ngdoc method
+ * @name $routeProvider#when
+ *
+ * @param {string} path Route path (matched against `$location.path`). If `$location.path`
+ * contains redundant trailing slash or is missing one, the route will still match and the
+ * `$location.path` will be updated to add or drop the trailing slash to exactly match the
+ * route definition.
+ *
+ * * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up
+ * to the next slash are matched and stored in `$routeParams` under the given `name`
+ * when the route matches.
+ * * `path` can contain named groups starting with a colon and ending with a star:
+ * e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name`
+ * when the route matches.
+ * * `path` can contain optional named groups with a question mark: e.g.`:name?`.
+ *
+ * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match
+ * `/color/brown/largecode/code/with/slashes/edit` and extract:
+ *
+ * * `color: brown`
+ * * `largecode: code/with/slashes`.
+ *
+ *
+ * @param {Object} route Mapping information to be assigned to `$route.current` on route
+ * match.
+ *
+ * Object properties:
+ *
+ * - `controller` – `{(string|Function)=}` – Controller fn that should be associated with
+ * newly created scope or the name of a {@link angular.Module#controller registered
+ * controller} if passed as a string.
+ * - `controllerAs` – `{string=}` – An identifier name for a reference to the controller.
+ * 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
+ * returns an html template as a string which should be used by {@link
+ * ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives.
+ * This property takes precedence over `templateUrl`.
+ *
+ * If `template` is a function, it will be called with the following parameters:
+ *
+ * - `{Array.}` - route parameters extracted from the current
+ * `$location.path()` by applying the current route
+ *
+ * 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}.
+ *
+ * If `templateUrl` is a function, it will be called with the following parameters:
+ *
+ * - `{Array.}` - route parameters extracted from the current
+ * `$location.path()` by applying the current route
+ *
+ * One of `templateUrl` or `template` is required.
+ *
+ * - `resolve` - `{Object.=}` - An optional map of dependencies which should
+ * 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
+ * instantiated.
+ * If all the promises are resolved successfully, the values of the resolved promises are
+ * injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is
+ * fired. If any of the promises are rejected the
+ * {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired.
+ * For easier access to the resolved dependencies from the template, the `resolve` map will
+ * be available on the scope of the route, under `$resolve` (by default) or a custom name
+ * specified by the `resolveAs` property (see below). This can be particularly useful, when
+ * working with {@link angular.Module#component components} as route templates.
+ *
+ * **Note:** If your scope already contains a property with this name, it will be hidden
+ * or overwritten. Make sure, you specify an appropriate name for this property, that
+ * does not collide with other properties on the scope.
+ *
+ * The map object is:
+ *
+ * - `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.
+ * 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
+ * resolved before its value is injected into the controller. Be aware that
+ * `ngRoute.$routeParams` will still refer to the previous route within these resolve
+ * functions. Use `$route.current.params` to access the new route parameters, instead.
+ *
+ * - `resolveAs` - `{string=}` - The name under which the `resolve` map will be available on
+ * the scope of the route. If omitted, defaults to `$resolve`.
+ *
+ * - `redirectTo` – `{(string|Function)=}` – value to update
+ * {@link ng.$location $location} path with and trigger route redirection.
+ *
+ * If `redirectTo` is a function, it will be called with the following parameters:
+ *
+ * - `{Object.}` - route parameters extracted from the current
+ * `$location.path()` by applying the current route templateUrl.
+ * - `{string}` - current `$location.path()`
+ * - `{Object}` - current `$location.search()`
+ *
+ * The custom `redirectTo` function is expected to return a string which will be used
+ * 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.
+ *
+ * - `[reloadOnSearch=true]` - `{boolean=}` - reload route when only `$location.search()`
+ * or `$location.hash()` changes.
+ *
+ * If the option is set to `false` and url in the browser changes, then
+ * `$routeUpdate` event is broadcasted on the root scope.
+ *
+ * - `[caseInsensitiveMatch=false]` - `{boolean=}` - match routes without being case sensitive
+ *
+ * If the option is set to `true`, then the particular route can be matched without being
+ * case sensitive
+ *
+ * @returns {Object} self
+ *
+ * @description
+ * Adds a new route definition to the `$route` service.
+ */
+ this.when = function(path, route) {
+ //copy original route object to preserve params inherited from proto chain
+ var routeCopy = shallowCopy(route);
+ if (angular.isUndefined(routeCopy.reloadOnSearch)) {
+ routeCopy.reloadOnSearch = true;
+ }
+ if (angular.isUndefined(routeCopy.caseInsensitiveMatch)) {
+ routeCopy.caseInsensitiveMatch = this.caseInsensitiveMatch;
+ }
+ routes[path] = angular.extend(
+ routeCopy,
+ path && pathRegExp(path, routeCopy)
+ );
+
+ // create redirection for trailing slashes
+ if (path) {
+ var redirectPath = (path[path.length - 1] === '/')
+ ? path.substr(0, path.length - 1)
+ : path + '/';
+
+ routes[redirectPath] = angular.extend(
+ {redirectTo: path},
+ pathRegExp(redirectPath, routeCopy)
+ );
+ }
+
+ return this;
+ };
+
+ /**
+ * @ngdoc property
+ * @name $routeProvider#caseInsensitiveMatch
+ * @description
+ *
+ * A boolean property indicating if routes defined
+ * using this provider should be matched using a case insensitive
+ * algorithm. Defaults to `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
+ * @name $routeProvider#otherwise
+ *
+ * @description
+ * Sets route definition that will be used on route change when no other route definition
+ * is matched.
+ *
+ * @param {Object|string} params Mapping information to be assigned to `$route.current`.
+ * If called with a string, the value maps to `redirectTo`.
+ * @returns {Object} self
+ */
+ this.otherwise = function(params) {
+ if (typeof params === 'string') {
+ params = {redirectTo: params};
+ }
+ this.when(null, params);
+ 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**:
+ * 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',
+ '$location',
+ '$routeParams',
+ '$q',
+ '$injector',
+ '$templateRequest',
+ '$sce',
+ '$browser',
+ function($rootScope, $location, $routeParams, $q, $injector, $templateRequest, $sce, $browser) {
+
+ /**
+ * @ngdoc service
+ * @name $route
+ * @requires $location
+ * @requires $routeParams
+ *
+ * @property {Object} current Reference to the current route definition.
+ * The route definition contains:
+ *
+ * - `controller`: The controller constructor as defined in the route definition.
+ * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for
+ * controller instantiation. The `locals` contain
+ * the resolved values of the `resolve` map. Additionally the `locals` also contain:
+ *
+ * - `$scope` - The current route scope.
+ * - `$template` - The current route template HTML.
+ *
+ * The `locals` will be assigned to the route scope's `$resolve` property. You can override
+ * the property name, using `resolveAs` in the route definition. See
+ * {@link ngRoute.$routeProvider $routeProvider} for more info.
+ *
+ * @property {Object} routes Object with all route configuration Objects as its properties.
+ *
+ * @description
+ * `$route` is used for deep-linking URLs to controllers and views (HTML partials).
+ * It watches `$location.url()` and tries to map the path to an existing route definition.
+ *
+ * Requires the {@link ngRoute `ngRoute`} module to be installed.
+ *
+ * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API.
+ *
+ * The `$route` service is typically used in conjunction with the
+ * {@link ngRoute.directive:ngView `ngView`} directive and the
+ * {@link ngRoute.$routeParams `$routeParams`} service.
+ *
+ * @example
+ * This example shows how changing the URL hash causes the `$route` to match a route against the
+ * URL, and the `ngView` pulls in the partial.
+ *
+ *
+ *
+ *
+ * Choose:
+ *
Moby |
+ *
Moby: Ch1 |
+ *
Gatsby |
+ *
Gatsby: Ch4 |
+ *
Scarlet Letter
+ *
+ *
+ *
+ *
+ *
+ *
$location.path() = {{$location.path()}}
+ *
$route.current.templateUrl = {{$route.current.templateUrl}}
+ *
$route.current.params = {{$route.current.params}}
+ *
$route.current.scope.name = {{$route.current.scope.name}}
+ *
$routeParams = {{$routeParams}}
+ *
+ *
+ *
+ *
+ * controller: {{name}}
+ * Book Id: {{params.bookId}}
+ *
+ *
+ *
+ * controller: {{name}}
+ * Book Id: {{params.bookId}}
+ * Chapter Id: {{params.chapterId}}
+ *
+ *
+ *
+ * angular.module('ngRouteExample', ['ngRoute'])
+ *
+ * .controller('MainController', function($scope, $route, $routeParams, $location) {
+ * $scope.$route = $route;
+ * $scope.$location = $location;
+ * $scope.$routeParams = $routeParams;
+ * })
+ *
+ * .controller('BookController', function($scope, $routeParams) {
+ * $scope.name = 'BookController';
+ * $scope.params = $routeParams;
+ * })
+ *
+ * .controller('ChapterController', function($scope, $routeParams) {
+ * $scope.name = 'ChapterController';
+ * $scope.params = $routeParams;
+ * })
+ *
+ * .config(function($routeProvider, $locationProvider) {
+ * $routeProvider
+ * .when('/Book/:bookId', {
+ * templateUrl: 'book.html',
+ * controller: 'BookController',
+ * resolve: {
+ * // I will cause a 1 second delay
+ * delay: function($q, $timeout) {
+ * var delay = $q.defer();
+ * $timeout(delay.resolve, 1000);
+ * return delay.promise;
+ * }
+ * }
+ * })
+ * .when('/Book/:bookId/ch/:chapterId', {
+ * templateUrl: 'chapter.html',
+ * controller: 'ChapterController'
+ * });
+ *
+ * // configure html5 to get links working on jsfiddle
+ * $locationProvider.html5Mode(true);
+ * });
+ *
+ *
+ *
+ *
+ * it('should load and compile correct template', function() {
+ * element(by.linkText('Moby: Ch1')).click();
+ * var content = element(by.css('[ng-view]')).getText();
+ * expect(content).toMatch(/controller: ChapterController/);
+ * expect(content).toMatch(/Book Id: Moby/);
+ * expect(content).toMatch(/Chapter Id: 1/);
+ *
+ * element(by.partialLinkText('Scarlet')).click();
+ *
+ * content = element(by.css('[ng-view]')).getText();
+ * expect(content).toMatch(/controller: BookController/);
+ * expect(content).toMatch(/Book Id: Scarlet/);
+ * });
+ *
+ *
+ */
+
+ /**
+ * @ngdoc event
+ * @name $route#$routeChangeStart
+ * @eventType broadcast on root scope
+ * @description
+ * Broadcasted before a route change. At this point the route services starts
+ * resolving all of the dependencies needed for the route change to occur.
+ * Typically this involves fetching the view template as well as any dependencies
+ * defined in `resolve` route property. Once all of the dependencies are resolved
+ * `$routeChangeSuccess` is fired.
+ *
+ * The route change (and the `$location` change that triggered it) can be prevented
+ * by calling `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on}
+ * for more details about event object.
+ *
+ * @param {Object} angularEvent Synthetic event object.
+ * @param {Route} next Future route information.
+ * @param {Route} current Current route information.
+ */
+
+ /**
+ * @ngdoc event
+ * @name $route#$routeChangeSuccess
+ * @eventType broadcast on root scope
+ * @description
+ * Broadcasted after a route change has happened successfully.
+ * The `resolve` dependencies are now available in the `current.locals` property.
+ *
+ * {@link ngRoute.directive:ngView ngView} listens for the directive
+ * to instantiate the controller and render the view.
+ *
+ * @param {Object} angularEvent Synthetic event object.
+ * @param {Route} current Current route information.
+ * @param {Route|Undefined} previous Previous route information, or undefined if current is
+ * first route entered.
+ */
+
+ /**
+ * @ngdoc event
+ * @name $route#$routeChangeError
+ * @eventType broadcast on root scope
+ * @description
+ * Broadcasted if a redirection function fails or any redirection or resolve promises are
+ * rejected.
+ *
+ * @param {Object} angularEvent Synthetic event object
+ * @param {Route} current Current route information.
+ * @param {Route} previous Previous route information.
+ * @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.
+ */
+
+ /**
+ * @ngdoc event
+ * @name $route#$routeUpdate
+ * @eventType broadcast on root scope
+ * @description
+ * The `reloadOnSearch` property has been set to false, and we are reusing the same
+ * instance of the Controller.
+ *
+ * @param {Object} angularEvent Synthetic event object
+ * @param {Route} current Current/previous route information.
+ */
+
+ var forceReload = false,
+ preparedRoute,
+ preparedRouteIsUpdateOnly,
+ $route = {
+ routes: routes,
+
+ /**
+ * @ngdoc method
+ * @name $route#reload
+ *
+ * @description
+ * Causes `$route` service to reload the current route even if
+ * {@link ng.$location $location} hasn't changed.
+ *
+ * As a result of that, {@link ngRoute.directive:ngView ngView}
+ * creates new scope and reinstantiates the controller.
+ */
+ reload: function() {
+ forceReload = true;
+
+ var fakeLocationEvent = {
+ defaultPrevented: false,
+ preventDefault: function fakePreventDefault() {
+ this.defaultPrevented = true;
+ forceReload = false;
+ }
+ };
+
+ $rootScope.$evalAsync(function() {
+ prepareRoute(fakeLocationEvent);
+ if (!fakeLocationEvent.defaultPrevented) commitRoute();
+ });
+ },
+
+ /**
+ * @ngdoc method
+ * @name $route#updateParams
+ *
+ * @description
+ * Causes `$route` service to update the current URL, replacing
+ * current route parameters with those specified in `newParams`.
+ * Provided property names that match the route's path segment
+ * definitions will be interpolated into the location's path, while
+ * remaining properties will be treated as query params.
+ *
+ * @param {!Object} newParams mapping of URL parameter names to values
+ */
+ updateParams: function(newParams) {
+ if (this.current && this.current.$$route) {
+ newParams = angular.extend({}, this.current.params, newParams);
+ $location.path(interpolate(this.current.$$route.originalPath, newParams));
+ // interpolate modifies newParams, only query params are left
+ $location.search(newParams);
+ } else {
+ throw $routeMinErr('norout', 'Tried updating route when with no current route');
+ }
+ }
+ };
+
+ $rootScope.$on('$locationChangeStart', prepareRoute);
+ $rootScope.$on('$locationChangeSuccess', commitRoute);
+
+ return $route;
+
+ /////////////////////////////////////////////////////
+
+ /**
+ * @param on {string} current url
+ * @param route {Object} route regexp to match the url against
+ * @return {?Object}
+ *
+ * @description
+ * Check if the route matches the current url.
+ *
+ * Inspired by match in
+ * visionmedia/express/lib/router/router.js.
+ */
+ function switchRouteMatcher(on, route) {
+ var keys = route.keys,
+ params = {};
+
+ if (!route.regexp) return null;
+
+ var m = route.regexp.exec(on);
+ if (!m) return null;
+
+ for (var i = 1, len = m.length; i < len; ++i) {
+ var key = keys[i - 1];
+
+ var val = m[i];
+
+ if (key && val) {
+ params[key.name] = val;
+ }
+ }
+ return params;
+ }
+
+ function prepareRoute($locationEvent) {
+ var lastRoute = $route.current;
+
+ preparedRoute = parseRoute();
+ preparedRouteIsUpdateOnly = preparedRoute && lastRoute && preparedRoute.$$route === lastRoute.$$route
+ && angular.equals(preparedRoute.pathParams, lastRoute.pathParams)
+ && !preparedRoute.reloadOnSearch && !forceReload;
+
+ if (!preparedRouteIsUpdateOnly && (lastRoute || preparedRoute)) {
+ if ($rootScope.$broadcast('$routeChangeStart', preparedRoute, lastRoute).defaultPrevented) {
+ if ($locationEvent) {
+ $locationEvent.preventDefault();
+ }
+ }
+ }
+ }
+
+ function commitRoute() {
+ var lastRoute = $route.current;
+ var nextRoute = preparedRoute;
+
+ if (preparedRouteIsUpdateOnly) {
+ lastRoute.params = nextRoute.params;
+ angular.copy(lastRoute.params, $routeParams);
+ $rootScope.$broadcast('$routeUpdate', lastRoute);
+ } else if (nextRoute || lastRoute) {
+ forceReload = false;
+ $route.current = nextRoute;
+
+ var nextRoutePromise = $q.resolve(nextRoute);
+
+ $browser.$$incOutstandingRequestCount();
+
+ nextRoutePromise.
+ then(getRedirectionData).
+ then(handlePossibleRedirection).
+ then(function(keepProcessingRoute) {
+ return keepProcessingRoute && nextRoutePromise.
+ then(resolveLocals).
+ then(function(locals) {
+ // after route change
+ 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);
+ }
+ }).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);
+ });
+ }
+ }
+
+ 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) {
+ if (route) {
+ var locals = angular.extend({}, route.resolve);
+ angular.forEach(locals, function(value, key) {
+ locals[key] = angular.isString(value) ?
+ $injector.get(value) :
+ $injector.invoke(value, null, null, key);
+ });
+ var template = getTemplateFor(route);
+ if (angular.isDefined(template)) {
+ locals['$template'] = template;
+ }
+ return $q.all(locals);
+ }
+ }
+
+ function getTemplateFor(route) {
+ var template, templateUrl;
+ if (angular.isDefined(template = route.template)) {
+ if (angular.isFunction(template)) {
+ template = template(route.params);
+ }
+ } else if (angular.isDefined(templateUrl = route.templateUrl)) {
+ if (angular.isFunction(templateUrl)) {
+ templateUrl = templateUrl(route.params);
+ }
+ if (angular.isDefined(templateUrl)) {
+ route.loadedTemplateUrl = $sce.valueOf(templateUrl);
+ template = $templateRequest(templateUrl);
+ }
+ }
+ return template;
+ }
+
+ /**
+ * @returns {Object} the current active route, by matching it against the URL
+ */
+ function parseRoute() {
+ // Match a route
+ var params, match;
+ angular.forEach(routes, function(route, path) {
+ if (!match && (params = switchRouteMatcher($location.path(), route))) {
+ match = inherit(route, {
+ params: angular.extend({}, $location.search(), params),
+ pathParams: params});
+ match.$$route = route;
+ }
+ });
+ // No route matched; fallback to "otherwise" route
+ return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
+ }
+
+ /**
+ * @returns {string} interpolation of the redirect path with the parameters
+ */
+ function interpolate(string, params) {
+ var result = [];
+ angular.forEach((string || '').split(':'), function(segment, i) {
+ if (i === 0) {
+ result.push(segment);
+ } else {
+ var segmentMatch = segment.match(/(\w+)(?:[?*])?(.*)/);
+ var key = segmentMatch[1];
+ result.push(params[key]);
+ result.push(segmentMatch[2] || '');
+ delete params[key];
+ }
+ });
+ return result.join('');
+ }
+ }];
+}
+
+instantiateRoute.$inject = ['$injector'];
+function instantiateRoute($injector) {
+ if (isEagerInstantiationEnabled) {
+ // Instantiate `$route`
+ $injector.get('$route');
+ }
+}
+
+ngRouteModule.provider('$routeParams', $RouteParamsProvider);
+
+
+/**
+ * @ngdoc service
+ * @name $routeParams
+ * @requires $route
+ * @this
+ *
+ * @description
+ * The `$routeParams` service allows you to retrieve the current set of route parameters.
+ *
+ * Requires the {@link ngRoute `ngRoute`} module to be installed.
+ *
+ * The route parameters are a combination of {@link ng.$location `$location`}'s
+ * {@link ng.$location#search `search()`} and {@link ng.$location#path `path()`}.
+ * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched.
+ *
+ * In case of parameter name collision, `path` params take precedence over `search` params.
+ *
+ * The service guarantees that the identity of the `$routeParams` object will remain unchanged
+ * (but its properties will likely change) even when a route change occurs.
+ *
+ * Note that the `$routeParams` are only updated *after* a route change completes successfully.
+ * This means that you cannot rely on `$routeParams` being correct in route resolve functions.
+ * Instead you can use `$route.current.params` to access the new route's parameters.
+ *
+ * @example
+ * ```js
+ * // Given:
+ * // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
+ * // Route: /Chapter/:chapterId/Section/:sectionId
+ * //
+ * // Then
+ * $routeParams ==> {chapterId:'1', sectionId:'2', search:'moby'}
+ * ```
+ */
+function $RouteParamsProvider() {
+ this.$get = function() { return {}; };
+}
+
+ngRouteModule.directive('ngView', ngViewFactory);
+ngRouteModule.directive('ngView', ngViewFillContentFactory);
+
+
+/**
+ * @ngdoc directive
+ * @name ngView
+ * @restrict ECA
+ *
+ * @description
+ * # Overview
+ * `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.
+ * Every time the current route changes, the included view changes with it according to the
+ * configuration of the `$route` service.
+ *
+ * Requires the {@link ngRoute `ngRoute`} module to be installed.
+ *
+ * @animations
+ * | Animation | Occurs |
+ * |----------------------------------|-------------------------------------|
+ * | {@link ng.$animate#enter enter} | when the new element is inserted to the DOM |
+ * | {@link ng.$animate#leave leave} | when the old element is removed from to the DOM |
+ *
+ * The enter and leave animation occur concurrently.
+ *
+ * @scope
+ * @priority 400
+ * @param {string=} onload Expression to evaluate whenever the view updates.
+ *
+ * @param {string=} autoscroll Whether `ngView` should call {@link ng.$anchorScroll
+ * $anchorScroll} to scroll the viewport after the view is updated.
+ *
+ * - If the attribute is not set, disable scrolling.
+ * - If the attribute is set without value, enable scrolling.
+ * - Otherwise enable scrolling only if the `autoscroll` attribute value evaluated
+ * as an expression yields a truthy value.
+ * @example
+
+
+
+ Choose:
+
Moby |
+
Moby: Ch1 |
+
Gatsby |
+
Gatsby: Ch4 |
+
Scarlet Letter
+
+
+
+
+
$location.path() = {{main.$location.path()}}
+
$route.current.templateUrl = {{main.$route.current.templateUrl}}
+
$route.current.params = {{main.$route.current.params}}
+
$routeParams = {{main.$routeParams}}
+
+
+
+
+
+ controller: {{book.name}}
+ Book Id: {{book.params.bookId}}
+
+
+
+
+
+ controller: {{chapter.name}}
+ Book Id: {{chapter.params.bookId}}
+ Chapter Id: {{chapter.params.chapterId}}
+
+
+
+
+ .view-animate-container {
+ position:relative;
+ height:100px!important;
+ background:white;
+ border:1px solid black;
+ height:40px;
+ overflow:hidden;
+ }
+
+ .view-animate {
+ padding:10px;
+ }
+
+ .view-animate.ng-enter, .view-animate.ng-leave {
+ transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
+
+ display:block;
+ width:100%;
+ border-left:1px solid black;
+
+ position:absolute;
+ top:0;
+ left:0;
+ right:0;
+ bottom:0;
+ padding:10px;
+ }
+
+ .view-animate.ng-enter {
+ left:100%;
+ }
+ .view-animate.ng-enter.ng-enter-active {
+ left:0;
+ }
+ .view-animate.ng-leave.ng-leave-active {
+ left:-100%;
+ }
+
+
+
+ angular.module('ngViewExample', ['ngRoute', 'ngAnimate'])
+ .config(['$routeProvider', '$locationProvider',
+ function($routeProvider, $locationProvider) {
+ $routeProvider
+ .when('/Book/:bookId', {
+ templateUrl: 'book.html',
+ controller: 'BookCtrl',
+ controllerAs: 'book'
+ })
+ .when('/Book/:bookId/ch/:chapterId', {
+ templateUrl: 'chapter.html',
+ controller: 'ChapterCtrl',
+ controllerAs: 'chapter'
+ });
+
+ $locationProvider.html5Mode(true);
+ }])
+ .controller('MainCtrl', ['$route', '$routeParams', '$location',
+ function MainCtrl($route, $routeParams, $location) {
+ this.$route = $route;
+ this.$location = $location;
+ this.$routeParams = $routeParams;
+ }])
+ .controller('BookCtrl', ['$routeParams', function BookCtrl($routeParams) {
+ this.name = 'BookCtrl';
+ this.params = $routeParams;
+ }])
+ .controller('ChapterCtrl', ['$routeParams', function ChapterCtrl($routeParams) {
+ this.name = 'ChapterCtrl';
+ this.params = $routeParams;
+ }]);
+
+
+
+
+ it('should load and compile correct template', function() {
+ element(by.linkText('Moby: Ch1')).click();
+ var content = element(by.css('[ng-view]')).getText();
+ expect(content).toMatch(/controller: ChapterCtrl/);
+ expect(content).toMatch(/Book Id: Moby/);
+ expect(content).toMatch(/Chapter Id: 1/);
+
+ element(by.partialLinkText('Scarlet')).click();
+
+ content = element(by.css('[ng-view]')).getText();
+ expect(content).toMatch(/controller: BookCtrl/);
+ expect(content).toMatch(/Book Id: Scarlet/);
+ });
+
+
+ */
+
+
+/**
+ * @ngdoc event
+ * @name ngView#$viewContentLoaded
+ * @eventType emit on the current ngView scope
+ * @description
+ * Emitted every time the ngView content is reloaded.
+ */
+ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate'];
+function ngViewFactory($route, $anchorScroll, $animate) {
+ return {
+ restrict: 'ECA',
+ terminal: true,
+ priority: 400,
+ transclude: 'element',
+ link: function(scope, $element, attr, ctrl, $transclude) {
+ var currentScope,
+ currentElement,
+ previousLeaveAnimation,
+ autoScrollExp = attr.autoscroll,
+ onloadExp = attr.onload || '';
+
+ scope.$on('$routeChangeSuccess', update);
+ update();
+
+ function cleanupLastView() {
+ if (previousLeaveAnimation) {
+ $animate.cancel(previousLeaveAnimation);
+ previousLeaveAnimation = null;
+ }
+
+ if (currentScope) {
+ currentScope.$destroy();
+ currentScope = null;
+ }
+ if (currentElement) {
+ previousLeaveAnimation = $animate.leave(currentElement);
+ previousLeaveAnimation.done(function(response) {
+ if (response !== false) previousLeaveAnimation = null;
+ });
+ currentElement = null;
+ }
+ }
+
+ function update() {
+ var locals = $route.current && $route.current.locals,
+ template = locals && locals.$template;
+
+ if (angular.isDefined(template)) {
+ var newScope = scope.$new();
+ var current = $route.current;
+
+ // Note: This will also link all children of ng-view that were contained in the original
+ // html. If that content contains controllers, ... they could pollute/change the scope.
+ // However, using ng-view on an element with additional content does not make sense...
+ // Note: We can't remove them in the cloneAttchFn of $transclude as that
+ // function is called before linking the content, which would apply child
+ // directives to non existing elements.
+ var clone = $transclude(newScope, function(clone) {
+ $animate.enter(clone, null, currentElement || $element).done(function onNgViewEnter(response) {
+ if (response !== false && angular.isDefined(autoScrollExp)
+ && (!autoScrollExp || scope.$eval(autoScrollExp))) {
+ $anchorScroll();
+ }
+ });
+ cleanupLastView();
+ });
+
+ currentElement = clone;
+ currentScope = current.scope = newScope;
+ currentScope.$emit('$viewContentLoaded');
+ currentScope.$eval(onloadExp);
+ } else {
+ cleanupLastView();
+ }
+ }
+ }
+ };
+}
+
+// This directive is called during the $transclude call of the first `ngView` directive.
+// It will replace and compile the content of the element with the loaded template.
+// We need this directive so that the element content is already filled when
+// the link function of another directive on the same element as ngView
+// is called.
+ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route'];
+function ngViewFillContentFactory($compile, $controller, $route) {
+ return {
+ restrict: 'ECA',
+ priority: -400,
+ link: function(scope, $element) {
+ var current = $route.current,
+ locals = current.locals;
+
+ $element.html(locals.$template);
+
+ var link = $compile($element.contents());
+
+ if (current.controller) {
+ locals.$scope = scope;
+ var controller = $controller(current.controller, locals);
+ if (current.controllerAs) {
+ scope[current.controllerAs] = controller;
+ }
+ $element.data('$ngControllerController', controller);
+ $element.children().data('$ngControllerController', controller);
+ }
+ scope[current.resolveAs || '$resolve'] = locals;
+
+ link(scope);
+ }
+ };
+}
+
+
+})(window, window.angular);
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-route/angular-route.min.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-route/angular-route.min.js
new file mode 100644
index 0000000000..3f985d1422
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-route/angular-route.min.js
@@ -0,0 +1,17 @@
+/*
+ AngularJS v1.6.4
+ (c) 2010-2017 Google, Inc. http://angularjs.org
+ License: MIT
+*/
+(function(J,d){'use strict';function A(d){k&&d.get("$route")}function B(t,u,g){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,f,b,c,m){function v(){l&&(g.cancel(l),l=null);n&&(n.$destroy(),n=null);p&&(l=g.leave(p),l.done(function(a){!1!==a&&(l=null)}),p=null)}function E(){var b=t.current&&t.current.locals;if(d.isDefined(b&&b.$template)){var b=a.$new(),c=t.current;p=m(b,function(b){g.enter(b,null,p||f).done(function(b){!1===b||!d.isDefined(w)||w&&!a.$eval(w)||u()});
+v()});n=c.scope=b;n.$emit("$viewContentLoaded");n.$eval(k)}else v()}var n,p,l,w=b.autoscroll,k=b.onload||"";a.$on("$routeChangeSuccess",E);E()}}}function C(d,k,g){return{restrict:"ECA",priority:-400,link:function(a,f){var b=g.current,c=b.locals;f.html(c.$template);var m=d(f.contents());if(b.controller){c.$scope=a;var v=k(b.controller,c);b.controllerAs&&(a[b.controllerAs]=v);f.data("$ngControllerController",v);f.children().data("$ngControllerController",v)}a[b.resolveAs||"$resolve"]=c;m(a)}}}var x,
+y,F,G,z=d.module("ngRoute",[]).info({angularVersion:"1.6.4"}).provider("$route",function(){function t(a,f){return d.extend(Object.create(a),f)}function u(a,d){var b=d.caseInsensitiveMatch,c={originalPath:a,regexp:a},g=c.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)(\*\?|[?*])?/g,function(a,b,d,c){a="?"===c||"*?"===c?"?":null;c="*"===c||"*?"===c?"*":null;g.push({name:d,optional:!!a});b=b||"";return""+(a?"":b)+"(?:"+(a?b:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([/$*])/g,
+"\\$1");c.regexp=new RegExp("^"+a+"$",b?"i":"");return c}x=d.isArray;y=d.isObject;F=d.isDefined;G=d.noop;var g={};this.when=function(a,f){var b;b=void 0;if(x(f)){b=b||[];for(var c=0,m=f.length;c",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/angular/angular.js/issues"
+ },
+ "homepage": "http://angularjs.org",
+ "jspm": {
+ "shim": {
+ "angular-route": {
+ "deps": ["angular"]
+ }
+ }
+ }
+}
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/LICENSE.md b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/LICENSE.md
new file mode 100644
index 0000000000..2c395eef1b
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Angular
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/README.md b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/README.md
new file mode 100644
index 0000000000..b84aaf6dbf
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/README.md
@@ -0,0 +1,68 @@
+# packaged angular-sanitize
+
+This repo is for distribution on `npm` and `bower`. The source for this module is in the
+[main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngSanitize).
+Please file issues and pull requests against that repo.
+
+## Install
+
+You can install this package either with `npm` or with `bower`.
+
+### npm
+
+```shell
+npm install angular-sanitize
+```
+
+Then add `ngSanitize` as a dependency for your app:
+
+```javascript
+angular.module('myApp', [require('angular-sanitize')]);
+```
+
+### bower
+
+```shell
+bower install angular-sanitize
+```
+
+Add a `
+```
+
+Then add `ngSanitize` as a dependency for your app:
+
+```javascript
+angular.module('myApp', ['ngSanitize']);
+```
+
+## Documentation
+
+Documentation is available on the
+[AngularJS docs site](http://docs.angularjs.org/api/ngSanitize).
+
+## License
+
+The MIT License
+
+Copyright (c) 2010-2015 Google, Inc. http://angularjs.org
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/angular-sanitize.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/angular-sanitize.js
new file mode 100644
index 0000000000..1d60fdb9db
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/angular-sanitize.js
@@ -0,0 +1,756 @@
+/**
+ * @license AngularJS v1.6.4
+ * (c) 2010-2017 Google, Inc. http://angularjs.org
+ * License: MIT
+ */
+(function(window, angular) {'use strict';
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Any commits to this file should be reviewed with security in mind. *
+ * Changes to this file can potentially create security vulnerabilities. *
+ * An approval from 2 Core members with history of modifying *
+ * this file is required. *
+ * *
+ * Does the change somehow allow for arbitrary javascript to be executed? *
+ * Or allows for someone to change the prototype of built-in objects? *
+ * Or gives undesired access to variables likes document or window? *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+var $sanitizeMinErr = angular.$$minErr('$sanitize');
+var bind;
+var extend;
+var forEach;
+var isDefined;
+var lowercase;
+var noop;
+var nodeContains;
+var htmlParser;
+var htmlSanitizeWriter;
+
+/**
+ * @ngdoc module
+ * @name ngSanitize
+ * @description
+ *
+ * # ngSanitize
+ *
+ * The `ngSanitize` module provides functionality to sanitize HTML.
+ *
+ *
+ *
+ *
+ * See {@link ngSanitize.$sanitize `$sanitize`} for usage.
+ */
+
+/**
+ * @ngdoc service
+ * @name $sanitize
+ * @kind function
+ *
+ * @description
+ * 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
+ * then serialized back to properly escaped html string. This means that no unsafe input can make
+ * it into the returned string.
+ *
+ * The whitelist for URL sanitization of attribute values is configured using the functions
+ * `aHrefSanitizationWhitelist` and `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider
+ * `$compileProvider`}.
+ *
+ * The input may also contain SVG markup if this is enabled via {@link $sanitizeProvider}.
+ *
+ * @param {string} html HTML input.
+ * @returns {string} Sanitized HTML.
+ *
+ * @example
+
+
+
+
+ Snippet:
+
+
+ Directive
+ How
+ Source
+ Rendered
+
+
+ ng-bind-html
+ Automatically uses $sanitize
+ <div ng-bind-html="snippet"> </div>
+
+
+
+ ng-bind-html
+ Bypass $sanitize by explicitly trusting the dangerous value
+
+ <div ng-bind-html="deliberatelyTrustDangerousSnippet()">
+</div>
+
+
+
+
+ ng-bind
+ Automatically escapes
+ <div ng-bind="snippet"> </div>
+
+
+
+
+
+
+ it('should sanitize the html snippet by default', function() {
+ expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')).
+ toBe('an html\nclick here \nsnippet
');
+ });
+
+ it('should inline raw snippet if bound to a trusted value', function() {
+ expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')).
+ toBe("an html\n" +
+ "click here \n" +
+ "snippet
");
+ });
+
+ it('should escape snippet without any filter', function() {
+ expect(element(by.css('#bind-default div')).getAttribute('innerHTML')).
+ toBe("<p style=\"color:blue\">an html\n" +
+ "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
+ "snippet</p>");
+ });
+
+ it('should update', function() {
+ element(by.model('snippet')).clear();
+ element(by.model('snippet')).sendKeys('new text ');
+ expect(element(by.css('#bind-html-with-sanitize div')).getAttribute('innerHTML')).
+ toBe('new text ');
+ expect(element(by.css('#bind-html-with-trust div')).getAttribute('innerHTML')).toBe(
+ 'new text ');
+ expect(element(by.css('#bind-default div')).getAttribute('innerHTML')).toBe(
+ "new <b onclick=\"alert(1)\">text</b>");
+ });
+
+
+ */
+
+
+/**
+ * @ngdoc provider
+ * @name $sanitizeProvider
+ * @this
+ *
+ * @description
+ * Creates and configures {@link $sanitize} instance.
+ */
+function $SanitizeProvider() {
+ var svgEnabled = false;
+
+ this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
+ if (svgEnabled) {
+ extend(validElements, svgElements);
+ }
+ return function(html) {
+ var buf = [];
+ htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
+ return !/^unsafe:/.test($$sanitizeUri(uri, isImage));
+ }));
+ return buf.join('');
+ };
+ }];
+
+
+ /**
+ * @ngdoc method
+ * @name $sanitizeProvider#enableSvg
+ * @kind function
+ *
+ * @description
+ * Enables a subset of svg to be supported by the sanitizer.
+ *
+ *
+ *
By enabling this setting without taking other precautions, you might expose your
+ * application to click-hijacking attacks. In these attacks, sanitized svg elements could be positioned
+ * outside of the containing element and be rendered over other elements on the page (e.g. a login
+ * link). Such behavior can then result in phishing incidents.
+ *
+ *
To protect against these, explicitly setup `overflow: hidden` css rule for all potential svg
+ * tags within the sanitized content:
+ *
+ *
+ *
+ *
+ * .rootOfTheIncludedContent svg {
+ * overflow: hidden !important;
+ * }
+ *
+ *
+ *
+ * @param {boolean=} flag Enable or disable SVG support in the sanitizer.
+ * @returns {boolean|ng.$sanitizeProvider} Returns the currently configured value if called
+ * without an argument or self for chaining otherwise.
+ */
+ this.enableSvg = function(enableSvg) {
+ if (isDefined(enableSvg)) {
+ svgEnabled = enableSvg;
+ return this;
+ } else {
+ return svgEnabled;
+ }
+ };
+
+ //////////////////////////////////////////////////////////////////////////////////////////////////
+ // Private stuff
+ //////////////////////////////////////////////////////////////////////////////////////////////////
+
+ bind = angular.bind;
+ extend = angular.extend;
+ forEach = angular.forEach;
+ isDefined = angular.isDefined;
+ lowercase = angular.lowercase;
+ noop = angular.noop;
+
+ htmlParser = htmlParserImpl;
+ 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
+ var SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
+ // Match everything outside of normal chars and " (quote character)
+ NON_ALPHANUMERIC_REGEXP = /([^#-~ |!])/g;
+
+
+ // Good source of info about elements and attributes
+ // http://dev.w3.org/html5/spec/Overview.html#semantics
+ // http://simon.html5.org/html-elements
+
+ // Safe Void Elements - HTML5
+ // http://dev.w3.org/html5/spec/Overview.html#void-elements
+ var voidElements = toMap('area,br,col,hr,img,wbr');
+
+ // Elements that you can, intentionally, leave open (and which close themselves)
+ // http://dev.w3.org/html5/spec/Overview.html#optional-tags
+ var optionalEndTagBlockElements = toMap('colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr'),
+ optionalEndTagInlineElements = toMap('rp,rt'),
+ optionalEndTagElements = extend({},
+ optionalEndTagInlineElements,
+ optionalEndTagBlockElements);
+
+ // Safe Block Elements - HTML5
+ var blockElements = extend({}, optionalEndTagBlockElements, toMap('address,article,' +
+ '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'));
+
+ // Inline Elements - HTML5
+ var inlineElements = extend({}, optionalEndTagInlineElements, toMap('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,' +
+ 'samp,small,span,strike,strong,sub,sup,time,tt,u,var'));
+
+ // SVG Elements
+ // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Elements
+ // Note: the elements animate,animateColor,animateMotion,animateTransform,set are intentionally omitted.
+ // 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,' +
+ 'hkern,image,linearGradient,line,marker,metadata,missing-glyph,mpath,path,polygon,polyline,' +
+ 'radialGradient,rect,stop,svg,switch,text,title,tspan');
+
+ // Blocked Elements (will be stripped)
+ var blockedElements = toMap('script,style');
+
+ var validElements = extend({},
+ voidElements,
+ blockElements,
+ inlineElements,
+ optionalEndTagElements);
+
+ //Attributes that have href and hence need to be sanitized
+ var uriAttrs = toMap('background,cite,href,longdesc,src,xlink:href');
+
+ var htmlAttrs = toMap('abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,' +
+ 'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,' +
+ 'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,' +
+ 'scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,' +
+ 'valign,value,vspace,width');
+
+ // SVG attributes (without "id" and "name" attributes)
+ // https://wiki.whatwg.org/wiki/Sanitization_rules#svg_Attributes
+ var svgAttrs = toMap('accent-height,accumulate,additive,alphabetic,arabic-form,ascent,' +
+ '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,' +
+ 'font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,' +
+ 'height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,' +
+ 'marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,' +
+ 'max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,' +
+ 'path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,' +
+ 'requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,' +
+ 'stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,' +
+ 'stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,' +
+ 'stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,' +
+ 'underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,' +
+ 'width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,' +
+ 'xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan', true);
+
+ var validAttrs = extend({},
+ uriAttrs,
+ svgAttrs,
+ htmlAttrs);
+
+ function toMap(str, lowercaseKeys) {
+ var obj = {}, items = str.split(','), i;
+ for (i = 0; i < items.length; i++) {
+ obj[lowercaseKeys ? lowercase(items[i]) : items[i]] = true;
+ }
+ return obj;
+ }
+
+ var inertBodyElement;
+ (function(window) {
+ var doc;
+ 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) {
+ inertBodyElement = bodyElements[0];
+ } else {
+ var html = doc.createElement('html');
+ inertBodyElement = doc.createElement('body');
+ html.appendChild(inertBodyElement);
+ doc.appendChild(html);
+ }
+ })(window);
+
+ /**
+ * @example
+ * htmlParser(htmlString, {
+ * start: function(tag, attrs) {},
+ * end: function(tag) {},
+ * chars: function(text) {},
+ * comment: function(text) {}
+ * });
+ *
+ * @param {string} html string
+ * @param {object} handler
+ */
+ function htmlParserImpl(html, handler) {
+ if (html === null || html === undefined) {
+ html = '';
+ } else if (typeof html !== 'string') {
+ html = '' + html;
+ }
+ inertBodyElement.innerHTML = html;
+
+ //mXSS protection
+ var mXSSAttempts = 5;
+ do {
+ if (mXSSAttempts === 0) {
+ throw $sanitizeMinErr('uinput', 'Failed to sanitize html because the input is unstable');
+ }
+ mXSSAttempts--;
+
+ // strip custom-namespaced attributes on IE<=11
+ if (window.document.documentMode) {
+ stripCustomNsAttrs(inertBodyElement);
+ }
+ html = inertBodyElement.innerHTML; //trigger mXSS
+ inertBodyElement.innerHTML = html;
+ } while (html !== inertBodyElement.innerHTML);
+
+ var node = inertBodyElement.firstChild;
+ while (node) {
+ switch (node.nodeType) {
+ case 1: // ELEMENT_NODE
+ handler.start(node.nodeName.toLowerCase(), attrToMap(node.attributes));
+ break;
+ case 3: // TEXT NODE
+ handler.chars(node.textContent);
+ break;
+ }
+
+ var nextNode;
+ if (!(nextNode = node.firstChild)) {
+ if (node.nodeType === 1) {
+ handler.end(node.nodeName.toLowerCase());
+ }
+ nextNode = getNonDescendant('nextSibling', node);
+ if (!nextNode) {
+ while (nextNode == null) {
+ node = getNonDescendant('parentNode', node);
+ if (node === inertBodyElement) break;
+ nextNode = getNonDescendant('nextSibling', node);
+ if (node.nodeType === 1) {
+ handler.end(node.nodeName.toLowerCase());
+ }
+ }
+ }
+ }
+ node = nextNode;
+ }
+
+ while ((node = inertBodyElement.firstChild)) {
+ inertBodyElement.removeChild(node);
+ }
+ }
+
+ function attrToMap(attrs) {
+ var map = {};
+ for (var i = 0, ii = attrs.length; i < ii; i++) {
+ var attr = attrs[i];
+ map[attr.name] = attr.value;
+ }
+ return map;
+ }
+
+
+ /**
+ * Escapes all potentially dangerous characters, so that the
+ * resulting string can be safely inserted into attribute or
+ * element text.
+ * @param value
+ * @returns {string} escaped text
+ */
+ function encodeEntities(value) {
+ return value.
+ replace(/&/g, '&').
+ replace(SURROGATE_PAIR_REGEXP, function(value) {
+ var hi = value.charCodeAt(0);
+ var low = value.charCodeAt(1);
+ return '' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
+ }).
+ replace(NON_ALPHANUMERIC_REGEXP, function(value) {
+ return '' + value.charCodeAt(0) + ';';
+ }).
+ replace(/ /g, '>');
+ }
+
+ /**
+ * create an HTML/XML writer which writes to buffer
+ * @param {Array} buf use buf.join('') to get out sanitized html string
+ * @returns {object} in the form of {
+ * start: function(tag, attrs) {},
+ * end: function(tag) {},
+ * chars: function(text) {},
+ * comment: function(text) {}
+ * }
+ */
+ function htmlSanitizeWriterImpl(buf, uriValidator) {
+ var ignoreCurrentElement = false;
+ var out = bind(buf, buf.push);
+ return {
+ start: function(tag, attrs) {
+ tag = lowercase(tag);
+ if (!ignoreCurrentElement && blockedElements[tag]) {
+ ignoreCurrentElement = tag;
+ }
+ if (!ignoreCurrentElement && validElements[tag] === true) {
+ out('<');
+ out(tag);
+ forEach(attrs, function(value, key) {
+ var lkey = lowercase(key);
+ var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
+ if (validAttrs[lkey] === true &&
+ (uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
+ out(' ');
+ out(key);
+ out('="');
+ out(encodeEntities(value));
+ out('"');
+ }
+ });
+ out('>');
+ }
+ },
+ end: function(tag) {
+ tag = lowercase(tag);
+ if (!ignoreCurrentElement && validElements[tag] === true && voidElements[tag] !== true) {
+ out('');
+ out(tag);
+ out('>');
+ }
+ // eslint-disable-next-line eqeqeq
+ if (tag == ignoreCurrentElement) {
+ ignoreCurrentElement = false;
+ }
+ },
+ chars: function(chars) {
+ if (!ignoreCurrentElement) {
+ out(encodeEntities(chars));
+ }
+ }
+ };
+ }
+
+
+ /**
+ * When IE9-11 comes across an unknown namespaced attribute e.g. 'xlink:foo' it adds 'xmlns:ns1' attribute to declare
+ * ns1 namespace and prefixes the attribute with 'ns1' (e.g. 'ns1:xlink:foo'). This is undesirable since we don't want
+ * to allow any of these custom attributes. This method strips them all.
+ *
+ * @param node Root element to process
+ */
+ function stripCustomNsAttrs(node) {
+ while (node) {
+ if (node.nodeType === window.Node.ELEMENT_NODE) {
+ var attrs = node.attributes;
+ for (var i = 0, l = attrs.length; i < l; i++) {
+ var attrNode = attrs[i];
+ var attrName = attrNode.name.toLowerCase();
+ if (attrName === 'xmlns:ns1' || attrName.lastIndexOf('ns1:', 0) === 0) {
+ node.removeAttributeNode(attrNode);
+ i--;
+ l--;
+ }
+ }
+ }
+
+ var nextNode = node.firstChild;
+ if (nextNode) {
+ stripCustomNsAttrs(nextNode);
+ }
+
+ node = getNonDescendant('nextSibling', node);
+ }
+ }
+
+ 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) {
+ var buf = [];
+ var writer = htmlSanitizeWriter(buf, noop);
+ writer.chars(chars);
+ return buf.join('');
+}
+
+
+// define ngSanitize module and register $sanitize service
+angular.module('ngSanitize', [])
+ .provider('$sanitize', $SanitizeProvider)
+ .info({ angularVersion: '1.6.4' });
+
+/**
+ * @ngdoc filter
+ * @name linky
+ * @kind function
+ *
+ * @description
+ * Finds links in text input and turns them into html links. Supports `http/https/ftp/mailto` and
+ * plain email address links.
+ *
+ * Requires the {@link ngSanitize `ngSanitize`} module to be installed.
+ *
+ * @param {string} text Input text.
+ * @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.
+ *
+ * Can be one of:
+ *
+ * - `object`: A map of attributes
+ * - `function`: Takes the url as a parameter and returns a map of attributes
+ *
+ * If the map of attributes contains a value for `target`, it overrides the value of
+ * the target parameter.
+ *
+ *
+ * @returns {string} Html-linkified and {@link $sanitize sanitized} text.
+ *
+ * @usage
+
+ *
+ * @example
+
+
+
+ Snippet:
+
+
+ Filter
+ Source
+ Rendered
+
+
+ linky filter
+
+ <div ng-bind-html="snippet | linky"> </div>
+
+
+
+
+
+
+ linky target
+
+ <div ng-bind-html="snippetWithSingleURL | linky:'_blank'"> </div>
+
+
+
+
+
+
+ linky custom attributes
+
+ <div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}"> </div>
+
+
+
+
+
+
+ no filter
+ <div ng-bind="snippet"> </div>
+
+
+
+
+
+ angular.module('linkyExample', ['ngSanitize'])
+ .controller('ExampleController', ['$scope', function($scope) {
+ $scope.snippet =
+ 'Pretty text with some links:\n' +
+ 'http://angularjs.org/,\n' +
+ 'mailto:us@somewhere.org,\n' +
+ 'another@somewhere.org,\n' +
+ 'and one more: ftp://127.0.0.1/.';
+ $scope.snippetWithSingleURL = 'http://angularjs.org/';
+ }]);
+
+
+ it('should linkify the snippet with urls', function() {
+ expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
+ toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' +
+ 'another@somewhere.org, and one more: ftp://127.0.0.1/.');
+ expect(element.all(by.css('#linky-filter a')).count()).toEqual(4);
+ });
+
+ it('should not linkify snippet without the linky filter', function() {
+ expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()).
+ toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' +
+ 'another@somewhere.org, and one more: ftp://127.0.0.1/.');
+ expect(element.all(by.css('#escaped-html a')).count()).toEqual(0);
+ });
+
+ it('should update', function() {
+ element(by.model('snippet')).clear();
+ element(by.model('snippet')).sendKeys('new http://link.');
+ expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
+ toBe('new http://link.');
+ expect(element.all(by.css('#linky-filter a')).count()).toEqual(1);
+ expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText())
+ .toBe('new http://link.');
+ });
+
+ it('should work with the target property', function() {
+ expect(element(by.id('linky-target')).
+ element(by.binding("snippetWithSingleURL | linky:'_blank'")).getText()).
+ toBe('http://angularjs.org/');
+ expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
+ });
+
+ it('should optionally add custom attributes', function() {
+ expect(element(by.id('linky-custom-attributes')).
+ element(by.binding("snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}")).getText()).
+ toBe('http://angularjs.org/');
+ expect(element(by.css('#linky-custom-attributes a')).getAttribute('rel')).toEqual('nofollow');
+ });
+
+
+ */
+angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
+ var LINKY_URL_REGEXP =
+ /((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i,
+ MAILTO_REGEXP = /^mailto:/i;
+
+ var linkyMinErr = angular.$$minErr('linky');
+ var isDefined = angular.isDefined;
+ var isFunction = angular.isFunction;
+ var isObject = angular.isObject;
+ var isString = angular.isString;
+
+ return function(text, target, attributes) {
+ if (text == null || text === '') return text;
+ if (!isString(text)) throw linkyMinErr('notstring', 'Expected string but received: {0}', text);
+
+ var attributesFn =
+ isFunction(attributes) ? attributes :
+ isObject(attributes) ? function getAttributesObject() {return attributes;} :
+ function getEmptyAttributesObject() {return {};};
+
+ var match;
+ var raw = text;
+ var html = [];
+ var url;
+ var i;
+ while ((match = raw.match(LINKY_URL_REGEXP))) {
+ // We can not end in these as they are sometimes found at the end of the sentence
+ url = match[0];
+ // if we did not match ftp/http/www/mailto then assume mailto
+ if (!match[2] && !match[4]) {
+ url = (match[3] ? 'http://' : 'mailto:') + url;
+ }
+ i = match.index;
+ addText(raw.substr(0, i));
+ addLink(url, match[0].replace(MAILTO_REGEXP, ''));
+ raw = raw.substring(i + match[0].length);
+ }
+ addText(raw);
+ return $sanitize(html.join(''));
+
+ function addText(text) {
+ if (!text) {
+ return;
+ }
+ html.push(sanitizeText(text));
+ }
+
+ function addLink(url, text) {
+ var key, linkAttributes = attributesFn(url);
+ html.push('
');
+ addText(text);
+ html.push(' ');
+ }
+ };
+}]);
+
+
+})(window, window.angular);
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/angular-sanitize.min.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/angular-sanitize.min.js
new file mode 100644
index 0000000000..b60ba73ccf
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/angular-sanitize.min.js
@@ -0,0 +1,16 @@
+/*
+ AngularJS v1.6.4
+ (c) 2010-2017 Google, Inc. http://angularjs.org
+ License: MIT
+*/
+(function(s,f){'use strict';function J(f){var k=[];v(k,B).chars(f);return k.join("")}var w=f.$$minErr("$sanitize"),C,k,D,E,q,B,F,G,v;f.module("ngSanitize",[]).provider("$sanitize",function(){function h(a,c){var b={},d=a.split(","),l;for(l=0;l
/g,">")}function I(a){for(;a;){if(a.nodeType===s.Node.ELEMENT_NODE)for(var c=a.attributes,b=0,d=c.length;b
"))},end:function(a){a=q(a);b||!0!==p[a]||!0===e[a]||(d(""),d(a),d(">"));a==b&&(b=!1)},chars:function(a){b||d(H(a))}}};F=s.Node.prototype.contains||function(a){return!!(this.compareDocumentPosition(a)&16)};var L=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,M=/([^#-~ |!])/g,e=h("area,br,col,hr,img,wbr"),y=h("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),m=h("rp,rt"),r=k({},m,y),y=k({},y,h("address,article,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")),
+m=k({},m,h("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,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),z=h("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,radialGradient,rect,stop,svg,switch,text,title,tspan"),A=h("script,style"),p=k({},e,y,m,r),n=h("background,cite,href,longdesc,src,xlink:href"),r=h("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,size,span,start,summary,tabindex,target,title,type,valign,value,vspace,width"),
+m=h("accent-height,accumulate,additive,alphabetic,arabic-form,ascent,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,font-style,font-variant,font-weight,from,fx,fy,g1,g2,glyph-name,gradientUnits,hanging,height,horiz-adv-x,horiz-origin-x,ideographic,k,keyPoints,keySplines,keyTimes,lang,marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mathematical,max,min,offset,opacity,orient,origin,overline-position,overline-thickness,panose-1,path,pathLength,points,preserveAspectRatio,r,refX,refY,repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,rotate,rx,ry,slope,stemh,stemv,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,stroke,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,systemLanguage,target,text-anchor,to,transform,type,u1,u2,underline-position,underline-thickness,unicode,unicode-range,units-per-em,values,version,viewBox,visibility,width,widths,x,x-height,x1,x2,xlink:actuate,xlink:arcrole,xlink:role,xlink:show,xlink:title,xlink:type,xml:base,xml:lang,xml:space,xmlns,xmlns:xlink,y,y1,y2,zoomAndPan",
+!0),u=k({},n,m,r),g;(function(a){if(a.document&&a.document.implementation)a=a.document.implementation.createHTMLDocument("inert");else throw w("noinert");var c=(a.documentElement||a.getDocumentElement()).getElementsByTagName("body");1===c.length?g=c[0]:(c=a.createElement("html"),g=a.createElement("body"),c.appendChild(g),a.appendChild(c))})(s)}).info({angularVersion:"1.6.4"});f.module("ngSanitize").filter("linky",["$sanitize",function(h){var k=/((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"\u201d\u2019]/i,
+q=/^mailto:/i,s=f.$$minErr("linky"),t=f.isDefined,x=f.isFunction,v=f.isObject,w=f.isString;return function(e,f,m){function r(a){a&&n.push(J(a))}function z(a,c){var b,d=A(a);n.push("');r(c);n.push(" ")}if(null==e||""===e)return e;if(!w(e))throw s("notstring",e);for(var A=x(m)?m:v(m)?function(){return m}:function(){return{}},p=e,n=[],u,g;e=p.match(k);)u=e[0],e[2]||
+e[4]||(u=(e[3]?"http://":"mailto:")+u),g=e.index,r(p.substr(0,g)),z(u,e[0].replace(q,"")),p=p.substring(g+e[0].length);r(p);return h(n.join(""))}}])})(window,window.angular);
+//# sourceMappingURL=angular-sanitize.min.js.map
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/angular-sanitize.min.js.map b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/angular-sanitize.min.js.map
new file mode 100644
index 0000000000..da32f79b31
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/angular-sanitize.min.js.map
@@ -0,0 +1,8 @@
+{
+"version":3,
+"file":"angular-sanitize.min.js",
+"lineCount":15,
+"mappings":"A;;;;;aAKC,SAAQ,CAACA,CAAD,CAASC,CAAT,CAAkB,CA4hB3BC,QAASA,EAAY,CAACC,CAAD,CAAQ,CAC3B,IAAIC,EAAM,EACGC,EAAAC,CAAmBF,CAAnBE,CAAwBC,CAAxBD,CACbH,MAAA,CAAaA,CAAb,CACA,OAAOC,EAAAI,KAAA,CAAS,EAAT,CAJoB,CA/gB7B,IAAIC,EAAkBR,CAAAS,SAAA,CAAiB,WAAjB,CAAtB,CACIC,CADJ,CAEIC,CAFJ,CAGIC,CAHJ,CAIIC,CAJJ,CAKIC,CALJ,CAMIR,CANJ,CAOIS,CAPJ,CAQIC,CARJ,CASIZ,CA+gBJJ,EAAAiB,OAAA,CAAe,YAAf,CAA6B,EAA7B,CAAAC,SAAA,CACY,WADZ,CA9YAC,QAA0B,EAAG,CA4J3BC,QAASA,EAAK,CAACC,CAAD,CAAMC,CAAN,CAAqB,CAAA,IAC7BC,EAAM,EADuB,CACnBC,EAAQH,CAAAI,MAAA,CAAU,GAAV,CADW,CACKC,CACtC,KAAKA,CAAL,CAAS,CAAT,CAAYA,CAAZ,CAAgBF,CAAAG,OAAhB,CAA8BD,CAAA,EAA9B,CACEH,CAAA,CAAID,CAAA,CAAgBR,CAAA,CAAUU,CAAA,CAAME,CAAN,CAAV,CAAhB,CAAsCF,CAAA,CAAME,CAAN,CAA1C,CAAA,CAAsD,CAAA,CAExD,OAAOH,EAL0B,CAsGnCK,QAASA,EAAS,CAACC,CAAD,CAAQ,CAExB,IADA,IAAIC,EAAM,EAAV,CACSJ,EAAI,CADb,CACgBK,EAAKF,CAAAF,OAArB,CAAmCD,CAAnC,CAAuCK,CAAvC,CAA2CL,CAAA,EAA3C,CAAgD,CAC9C,IAAIM,EAAOH,CAAA,CAAMH,CAAN,CACXI,EAAA,CAAIE,CAAAC,KAAJ,CAAA,CAAiBD,CAAAE,MAF6B,CAIhD,MAAOJ,EANiB,CAiB1BK,QAASA,EAAc,CAACD,CAAD,CAAQ,CAC7B,MAAOA,EAAAE,QAAA,CACG,IADH,CACS,OADT,CAAAA,QAAA,CAEGC,CAFH,CAE0B,QAAQ,CAACH,CAAD,CAAQ,CAC7C,IAAII,EAAKJ,CAAAK,WAAA,CAAiB,CAAjB,CACLC,EAAAA,CAAMN,CAAAK,WAAA,CAAiB,CAAjB,CACV,OAAO,IAAP,EAAgC,IAAhC,EAAiBD,CAAjB;AAAsB,KAAtB,GAA0CE,CAA1C,CAAgD,KAAhD,EAA0D,KAA1D,EAAqE,GAHxB,CAF1C,CAAAJ,QAAA,CAOGK,CAPH,CAO4B,QAAQ,CAACP,CAAD,CAAQ,CAC/C,MAAO,IAAP,CAAcA,CAAAK,WAAA,CAAiB,CAAjB,CAAd,CAAoC,GADW,CAP5C,CAAAH,QAAA,CAUG,IAVH,CAUS,MAVT,CAAAA,QAAA,CAWG,IAXH,CAWS,MAXT,CADsB,CAgF/BM,QAASA,EAAkB,CAACC,CAAD,CAAO,CAChC,IAAA,CAAOA,CAAP,CAAA,CAAa,CACX,GAAIA,CAAAC,SAAJ,GAAsB7C,CAAA8C,KAAAC,aAAtB,CAEE,IADA,IAAIjB,EAAQc,CAAAI,WAAZ,CACSrB,EAAI,CADb,CACgBsB,EAAInB,CAAAF,OAApB,CAAkCD,CAAlC,CAAsCsB,CAAtC,CAAyCtB,CAAA,EAAzC,CAA8C,CAC5C,IAAIuB,EAAWpB,CAAA,CAAMH,CAAN,CAAf,CACIwB,EAAWD,CAAAhB,KAAAkB,YAAA,EACf,IAAiB,WAAjB,GAAID,CAAJ,EAAoE,CAApE,GAAgCA,CAAAE,YAAA,CAAqB,MAArB,CAA6B,CAA7B,CAAhC,CACET,CAAAU,oBAAA,CAAyBJ,CAAzB,CAEA,CADAvB,CAAA,EACA,CAAAsB,CAAA,EAN0C,CAYhD,CADIM,CACJ,CADeX,CAAAY,WACf,GACEb,CAAA,CAAmBY,CAAnB,CAGFX,EAAA,CAAOa,CAAA,CAAiB,aAAjB,CAAgCb,CAAhC,CAnBI,CADmB,CAwBlCa,QAASA,EAAgB,CAACC,CAAD,CAAWd,CAAX,CAAiB,CAExC,IAAIW,EAAWX,CAAA,CAAKc,CAAL,CACf,IAAIH,CAAJ,EAAgBvC,CAAA2C,KAAA,CAAkBf,CAAlB,CAAwBW,CAAxB,CAAhB,CACE,KAAM9C,EAAA,CAAgB,QAAhB,CAA2FmC,CAAAgB,UAA3F,EAA6GhB,CAAAiB,UAA7G,CAAN,CAEF,MAAON,EANiC,CA1X1C,IAAIO,EAAa,CAAA,CAEjB,KAAAC,KAAA;AAAY,CAAC,eAAD,CAAkB,QAAQ,CAACC,CAAD,CAAgB,CAChDF,CAAJ,EACElD,CAAA,CAAOqD,CAAP,CAAsBC,CAAtB,CAEF,OAAO,SAAQ,CAACC,CAAD,CAAO,CACpB,IAAI/D,EAAM,EACVa,EAAA,CAAWkD,CAAX,CAAiB9D,CAAA,CAAmBD,CAAnB,CAAwB,QAAQ,CAACgE,CAAD,CAAMC,CAAN,CAAe,CAC9D,MAAO,CAAC,UAAAC,KAAA,CAAgBN,CAAA,CAAcI,CAAd,CAAmBC,CAAnB,CAAhB,CADsD,CAA/C,CAAjB,CAGA,OAAOjE,EAAAI,KAAA,CAAS,EAAT,CALa,CAJ8B,CAA1C,CA4CZ,KAAA+D,UAAA,CAAiBC,QAAQ,CAACD,CAAD,CAAY,CACnC,MAAIzD,EAAA,CAAUyD,CAAV,CAAJ,EACET,CACO,CADMS,CACN,CAAA,IAFT,EAIST,CAL0B,CAarCnD,EAAA,CAAOV,CAAAU,KACPC,EAAA,CAASX,CAAAW,OACTC,EAAA,CAAUZ,CAAAY,QACVC,EAAA,CAAYb,CAAAa,UACZC,EAAA,CAAYd,CAAAc,UACZR,EAAA,CAAON,CAAAM,KAEPU,EAAA,CAmIAwD,QAAuB,CAACN,CAAD,CAAOO,CAAP,CAAgB,CACxB,IAAb,GAAIP,CAAJ,EAA8BQ,IAAAA,EAA9B,GAAqBR,CAArB,CACEA,CADF,CACS,EADT,CAE2B,QAF3B,GAEW,MAAOA,EAFlB,GAGEA,CAHF,CAGS,EAHT,CAGcA,CAHd,CAKAS,EAAAC,UAAA,CAA6BV,CAG7B,KAAIW,EAAe,CACnB,GAAG,CACD,GAAqB,CAArB,GAAIA,CAAJ,CACE,KAAMrE,EAAA,CAAgB,QAAhB,CAAN,CAEFqE,CAAA,EAGI9E,EAAA+E,SAAAC,aAAJ,EACErC,CAAA,CAAmBiC,CAAnB,CAEFT,EAAA,CAAOS,CAAAC,UACPD,EAAAC,UAAA,CAA6BV,CAX5B,CAAH,MAYSA,CAZT,GAYkBS,CAAAC,UAZlB,CAeA,KADIjC,CACJ,CADWgC,CAAApB,WACX,CAAOZ,CAAP,CAAA,CAAa,CACX,OAAQA,CAAAC,SAAR,EACE,KAAK,CAAL,CACE6B,CAAAO,MAAA,CAAcrC,CAAAsC,SAAA9B,YAAA,EAAd;AAA2CvB,CAAA,CAAUe,CAAAI,WAAV,CAA3C,CACA,MACF,MAAK,CAAL,CACE0B,CAAAvE,MAAA,CAAcyC,CAAAuC,YAAd,CALJ,CASA,IAAI5B,CACJ,IAAM,EAAAA,CAAA,CAAWX,CAAAY,WAAX,CAAN,GACwB,CAIjBD,GAJDX,CAAAC,SAICU,EAHHmB,CAAAU,IAAA,CAAYxC,CAAAsC,SAAA9B,YAAA,EAAZ,CAGGG,CADLA,CACKA,CADME,CAAA,CAAiB,aAAjB,CAAgCb,CAAhC,CACNW,CAAAA,CAAAA,CALP,EAMI,IAAA,CAAmB,IAAnB,EAAOA,CAAP,CAAA,CAAyB,CACvBX,CAAA,CAAOa,CAAA,CAAiB,YAAjB,CAA+Bb,CAA/B,CACP,IAAIA,CAAJ,GAAagC,CAAb,CAA+B,KAC/BrB,EAAA,CAAWE,CAAA,CAAiB,aAAjB,CAAgCb,CAAhC,CACW,EAAtB,GAAIA,CAAAC,SAAJ,EACE6B,CAAAU,IAAA,CAAYxC,CAAAsC,SAAA9B,YAAA,EAAZ,CALqB,CAU7BR,CAAA,CAAOW,CA3BI,CA8Bb,IAAA,CAAQX,CAAR,CAAegC,CAAApB,WAAf,CAAA,CACEoB,CAAAS,YAAA,CAA6BzC,CAA7B,CAxDmC,CAlIvCvC,EAAA,CAwOAiF,QAA+B,CAAClF,CAAD,CAAMmF,CAAN,CAAoB,CACjD,IAAIC,EAAuB,CAAA,CAA3B,CACIC,EAAM9E,CAAA,CAAKP,CAAL,CAAUA,CAAAsF,KAAV,CACV,OAAO,CACLT,MAAOA,QAAQ,CAACU,CAAD,CAAM7D,CAAN,CAAa,CAC1B6D,CAAA,CAAM5E,CAAA,CAAU4E,CAAV,CACDH,EAAAA,CAAL,EAA6BI,CAAA,CAAgBD,CAAhB,CAA7B,GACEH,CADF,CACyBG,CADzB,CAGKH,EAAL,EAAoD,CAAA,CAApD,GAA6BvB,CAAA,CAAc0B,CAAd,CAA7B,GACEF,CAAA,CAAI,GAAJ,CAcA,CAbAA,CAAA,CAAIE,CAAJ,CAaA,CAZA9E,CAAA,CAAQiB,CAAR,CAAe,QAAQ,CAACK,CAAD,CAAQ0D,CAAR,CAAa,CAClC,IAAIC,EAAO/E,CAAA,CAAU8E,CAAV,CAAX,CACIxB,EAAmB,KAAnBA,GAAWsB,CAAXtB,EAAqC,KAArCA,GAA4ByB,CAA5BzB,EAAyD,YAAzDA;AAAgDyB,CAC3B,EAAA,CAAzB,GAAIC,CAAA,CAAWD,CAAX,CAAJ,EACsB,CAAA,CADtB,GACGE,CAAA,CAASF,CAAT,CADH,EAC8B,CAAAP,CAAA,CAAapD,CAAb,CAAoBkC,CAApB,CAD9B,GAEEoB,CAAA,CAAI,GAAJ,CAIA,CAHAA,CAAA,CAAII,CAAJ,CAGA,CAFAJ,CAAA,CAAI,IAAJ,CAEA,CADAA,CAAA,CAAIrD,CAAA,CAAeD,CAAf,CAAJ,CACA,CAAAsD,CAAA,CAAI,GAAJ,CANF,CAHkC,CAApC,CAYA,CAAAA,CAAA,CAAI,GAAJ,CAfF,CAL0B,CADvB,CAwBLL,IAAKA,QAAQ,CAACO,CAAD,CAAM,CACjBA,CAAA,CAAM5E,CAAA,CAAU4E,CAAV,CACDH,EAAL,EAAoD,CAAA,CAApD,GAA6BvB,CAAA,CAAc0B,CAAd,CAA7B,EAAkF,CAAA,CAAlF,GAA4DM,CAAA,CAAaN,CAAb,CAA5D,GACEF,CAAA,CAAI,IAAJ,CAEA,CADAA,CAAA,CAAIE,CAAJ,CACA,CAAAF,CAAA,CAAI,GAAJ,CAHF,CAMIE,EAAJ,EAAWH,CAAX,GACEA,CADF,CACyB,CAAA,CADzB,CARiB,CAxBd,CAoCLrF,MAAOA,QAAQ,CAACA,CAAD,CAAQ,CAChBqF,CAAL,EACEC,CAAA,CAAIrD,CAAA,CAAejC,CAAf,CAAJ,CAFmB,CApClB,CAH0C,CAtOnDa,EAAA,CAAehB,CAAA8C,KAAAoD,UAAAC,SAAf,EAA8D,QAAQ,CAACC,CAAD,CAAM,CAE1E,MAAO,CAAG,EAAA,IAAAC,wBAAA,CAA6BD,CAA7B,CAAA,CAAoC,EAApC,CAFgE,CAtEjD,KA4EvB9D,EAAwB,iCA5ED,CA8EzBI,EAA0B,cA9ED,CAuFvBuD,EAAe5E,CAAA,CAAM,wBAAN,CAvFQ,CA2FvBiF,EAA8BjF,CAAA,CAAM,gDAAN,CA3FP,CA4FvBkF,EAA+BlF,CAAA,CAAM,OAAN,CA5FR,CA6FvBmF,EAAyB5F,CAAA,CAAO,EAAP,CACe2F,CADf,CAEeD,CAFf,CA7FF,CAkGvBG,EAAgB7F,CAAA,CAAO,EAAP,CAAW0F,CAAX,CAAwCjF,CAAA,CAAM,qKAAN,CAAxC,CAlGO;AAuGvBqF,EAAiB9F,CAAA,CAAO,EAAP,CAAW2F,CAAX,CAAyClF,CAAA,CAAM,2JAAN,CAAzC,CAvGM,CA+GvB6C,EAAc7C,CAAA,CAAM,wNAAN,CA/GS,CAoHvBuE,EAAkBvE,CAAA,CAAM,cAAN,CApHK,CAsHvB4C,EAAgBrD,CAAA,CAAO,EAAP,CACeqF,CADf,CAEeQ,CAFf,CAGeC,CAHf,CAIeF,CAJf,CAtHO,CA6HvBR,EAAW3E,CAAA,CAAM,8CAAN,CA7HY,CA+HvBsF,EAAYtF,CAAA,CAAM,kTAAN,CA/HW;AAuIvBuF,EAAWvF,CAAA,CAAM,guCAAN;AAcoE,CAAA,CAdpE,CAvIY,CAuJvB0E,EAAanF,CAAA,CAAO,EAAP,CACeoF,CADf,CAEeY,CAFf,CAGeD,CAHf,CAvJU,CAoKvB/B,CACH,UAAQ,CAAC5E,CAAD,CAAS,CAEhB,GAAIA,CAAA+E,SAAJ,EAAuB/E,CAAA+E,SAAA8B,eAAvB,CACEC,CAAA,CAAM9G,CAAA+E,SAAA8B,eAAAE,mBAAA,CAAkD,OAAlD,CADR,KAGE,MAAMtG,EAAA,CAAgB,SAAhB,CAAN,CAGF,IAAIuG,EAAeC,CADFH,CAAAI,gBACED,EADqBH,CAAAK,mBAAA,EACrBF,sBAAA,CAAgC,MAAhC,CAGS,EAA5B,GAAID,CAAApF,OAAJ,CACEgD,CADF,CACqBoC,CAAA,CAAa,CAAb,CADrB,EAGM7C,CAGJ,CAHW2C,CAAAM,cAAA,CAAkB,MAAlB,CAGX,CAFAxC,CAEA,CAFmBkC,CAAAM,cAAA,CAAkB,MAAlB,CAEnB,CADAjD,CAAAkD,YAAA,CAAiBzC,CAAjB,CACA,CAAAkC,CAAAO,YAAA,CAAgBlD,CAAhB,CANF,CAXgB,CAAjB,CAAD,CAmBGnE,CAnBH,CArK2B,CA8Y7B,CAAAsH,KAAA,CAEQ,CAAEC,eAAgB,OAAlB,CAFR,CAmIAtH,EAAAiB,OAAA,CAAe,YAAf,CAAAsG,OAAA,CAAoC,OAApC,CAA6C,CAAC,WAAD,CAAc,QAAQ,CAACC,CAAD,CAAY,CAAA,IACzEC,EACE,yFAFuE;AAGzEC,EAAgB,WAHyD,CAKzEC,EAAc3H,CAAAS,SAAA,CAAiB,OAAjB,CAL2D,CAMzEI,EAAYb,CAAAa,UAN6D,CAOzE+G,EAAa5H,CAAA4H,WAP4D,CAQzEC,EAAW7H,CAAA6H,SAR8D,CASzEC,EAAW9H,CAAA8H,SAEf,OAAO,SAAQ,CAACC,CAAD,CAAOC,CAAP,CAAejF,CAAf,CAA2B,CA6BxCkF,QAASA,EAAO,CAACF,CAAD,CAAO,CAChBA,CAAL,EAGA7D,CAAAuB,KAAA,CAAUxF,CAAA,CAAa8H,CAAb,CAAV,CAJqB,CAOvBG,QAASA,EAAO,CAACC,CAAD,CAAMJ,CAAN,CAAY,CAAA,IACtBnC,CADsB,CACjBwC,EAAiBC,CAAA,CAAaF,CAAb,CAC1BjE,EAAAuB,KAAA,CAAU,KAAV,CAEA,KAAKG,CAAL,GAAYwC,EAAZ,CACElE,CAAAuB,KAAA,CAAUG,CAAV,CAAgB,IAAhB,CAAuBwC,CAAA,CAAexC,CAAf,CAAvB,CAA6C,IAA7C,CAGE,EAAA/E,CAAA,CAAUmH,CAAV,CAAJ,EAA2B,QAA3B,EAAuCI,EAAvC,EACElE,CAAAuB,KAAA,CAAU,UAAV,CACUuC,CADV,CAEU,IAFV,CAIF9D,EAAAuB,KAAA,CAAU,QAAV,CACU0C,CAAA/F,QAAA,CAAY,IAAZ,CAAkB,QAAlB,CADV,CAEU,IAFV,CAGA6F,EAAA,CAAQF,CAAR,CACA7D,EAAAuB,KAAA,CAAU,MAAV,CAjB0B,CAnC5B,GAAY,IAAZ,EAAIsC,CAAJ,EAA6B,EAA7B,GAAoBA,CAApB,CAAiC,MAAOA,EACxC,IAAK,CAAAD,CAAA,CAASC,CAAT,CAAL,CAAqB,KAAMJ,EAAA,CAAY,WAAZ,CAA8DI,CAA9D,CAAN,CAYrB,IAVA,IAAIM,EACFT,CAAA,CAAW7E,CAAX,CAAA,CAAyBA,CAAzB,CACA8E,CAAA,CAAS9E,CAAT,CAAA,CAAuBuF,QAA4B,EAAG,CAAC,MAAOvF,EAAR,CAAtD,CACAwF,QAAiC,EAAG,CAAC,MAAO,EAAR,CAHtC,CAMIC,EAAMT,CANV,CAOI7D,EAAO,EAPX,CAQIiE,CARJ,CASIzG,CACJ,CAAQ+G,CAAR,CAAgBD,CAAAC,MAAA,CAAUhB,CAAV,CAAhB,CAAA,CAEEU,CAQA,CARMM,CAAA,CAAM,CAAN,CAQN,CANKA,CAAA,CAAM,CAAN,CAML;AANkBA,CAAA,CAAM,CAAN,CAMlB,GALEN,CAKF,EALSM,CAAA,CAAM,CAAN,CAAA,CAAW,SAAX,CAAuB,SAKhC,EAL6CN,CAK7C,EAHAzG,CAGA,CAHI+G,CAAAC,MAGJ,CAFAT,CAAA,CAAQO,CAAAG,OAAA,CAAW,CAAX,CAAcjH,CAAd,CAAR,CAEA,CADAwG,CAAA,CAAQC,CAAR,CAAaM,CAAA,CAAM,CAAN,CAAArG,QAAA,CAAiBsF,CAAjB,CAAgC,EAAhC,CAAb,CACA,CAAAc,CAAA,CAAMA,CAAAI,UAAA,CAAclH,CAAd,CAAkB+G,CAAA,CAAM,CAAN,CAAA9G,OAAlB,CAERsG,EAAA,CAAQO,CAAR,CACA,OAAOhB,EAAA,CAAUtD,CAAA3D,KAAA,CAAU,EAAV,CAAV,CA3BiC,CAXmC,CAAlC,CAA7C,CAxqB2B,CAA1B,CAAD,CA8uBGR,MA9uBH,CA8uBWA,MAAAC,QA9uBX;",
+"sources":["angular-sanitize.js"],
+"names":["window","angular","sanitizeText","chars","buf","htmlSanitizeWriter","writer","noop","join","$sanitizeMinErr","$$minErr","bind","extend","forEach","isDefined","lowercase","nodeContains","htmlParser","module","provider","$SanitizeProvider","toMap","str","lowercaseKeys","obj","items","split","i","length","attrToMap","attrs","map","ii","attr","name","value","encodeEntities","replace","SURROGATE_PAIR_REGEXP","hi","charCodeAt","low","NON_ALPHANUMERIC_REGEXP","stripCustomNsAttrs","node","nodeType","Node","ELEMENT_NODE","attributes","l","attrNode","attrName","toLowerCase","lastIndexOf","removeAttributeNode","nextNode","firstChild","getNonDescendant","propName","call","outerHTML","outerText","svgEnabled","$get","$$sanitizeUri","validElements","svgElements","html","uri","isImage","test","enableSvg","this.enableSvg","htmlParserImpl","handler","undefined","inertBodyElement","innerHTML","mXSSAttempts","document","documentMode","start","nodeName","textContent","end","removeChild","htmlSanitizeWriterImpl","uriValidator","ignoreCurrentElement","out","push","tag","blockedElements","key","lkey","validAttrs","uriAttrs","voidElements","prototype","contains","arg","compareDocumentPosition","optionalEndTagBlockElements","optionalEndTagInlineElements","optionalEndTagElements","blockElements","inlineElements","htmlAttrs","svgAttrs","implementation","doc","createHTMLDocument","bodyElements","getElementsByTagName","documentElement","getDocumentElement","createElement","appendChild","info","angularVersion","filter","$sanitize","LINKY_URL_REGEXP","MAILTO_REGEXP","linkyMinErr","isFunction","isObject","isString","text","target","addText","addLink","url","linkAttributes","attributesFn","getAttributesObject","getEmptyAttributesObject","raw","match","index","substr","substring"]
+}
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/bower.json b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/bower.json
new file mode 100644
index 0000000000..f4b46c9baf
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/bower.json
@@ -0,0 +1,10 @@
+{
+ "name": "angular-sanitize",
+ "version": "1.6.4",
+ "license": "MIT",
+ "main": "./angular-sanitize.js",
+ "ignore": [],
+ "dependencies": {
+ "angular": "1.6.4"
+ }
+}
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/index.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/index.js
new file mode 100644
index 0000000000..dd5d22e4a5
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/index.js
@@ -0,0 +1,2 @@
+require('./angular-sanitize');
+module.exports = 'ngSanitize';
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/package.json b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/package.json
new file mode 100644
index 0000000000..6739401d7c
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-sanitize/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "angular-sanitize",
+ "version": "1.6.4",
+ "description": "AngularJS module for sanitizing HTML",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/angular/angular.js.git"
+ },
+ "keywords": [
+ "angular",
+ "framework",
+ "browser",
+ "html",
+ "client-side"
+ ],
+ "author": "Angular Core Team ",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/angular/angular.js/issues"
+ },
+ "homepage": "http://angularjs.org",
+ "jspm": {
+ "shim": {
+ "angular-sanitize": {
+ "deps": ["angular"]
+ }
+ }
+ }
+}
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate-loader-url/README.md b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate-loader-url/README.md
new file mode 100644
index 0000000000..a3c5374e69
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate-loader-url/README.md
@@ -0,0 +1,29 @@
+# angular-translate-loader-url (bower shadow repository)
+
+This is the _Bower shadow_ repository for *angular-translate-loader-url*.
+
+## Bugs and issues
+
+Please file any issues and bugs in our main repository at [angular-translate/angular-translate](https://github.com/angular-translate/angular-translate/issues).
+
+## Usage
+
+### via Bower
+
+```bash
+$ bower install angular-translate-loader-url
+```
+
+### via NPM
+
+```bash
+$ npm install angular-translate-loader-url
+```
+
+### via cdnjs
+
+Please have a look at https://cdnjs.com/libraries/angular-translate-loader-url for specific versions.
+
+## License
+
+Licensed under MIT. See more details at [angular-translate/angular-translate](https://github.com/angular-translate/angular-translate).
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate-loader-url/angular-translate-loader-url.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate-loader-url/angular-translate-loader-url.js
new file mode 100644
index 0000000000..e71c8bdbaf
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate-loader-url/angular-translate-loader-url.js
@@ -0,0 +1,73 @@
+/*!
+ * angular-translate - v2.15.1 - 2017-03-04
+ *
+ * Copyright (c) 2017 The angular-translate team, Pascal Precht; Licensed MIT
+ */
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module unless amdModuleId is set
+ define([], function () {
+ return (factory());
+ });
+ } else if (typeof exports === 'object') {
+ // Node. Does not work with strict CommonJS, but
+ // only CommonJS-like environments that support module.exports,
+ // like Node.
+ module.exports = factory();
+ } else {
+ factory();
+ }
+}(this, function () {
+
+$translateUrlLoader.$inject = ['$q', '$http'];
+angular.module('pascalprecht.translate')
+/**
+ * @ngdoc object
+ * @name pascalprecht.translate.$translateUrlLoader
+ * @requires $q
+ * @requires $http
+ *
+ * @description
+ * Creates a loading function for a typical dynamic url pattern:
+ * "locale.php?lang=en_US", "locale.php?lang=de_DE", "locale.php?language=nl_NL" etc.
+ * Prefixing the specified url, the current requested, language id will be applied
+ * with "?{queryParameter}={key}".
+ * Using this service, the response of these urls must be an object of
+ * key-value pairs.
+ *
+ * @param {object} options Options object, which gets the url, key and
+ * optional queryParameter ('lang' is used by default).
+ */
+.factory('$translateUrlLoader', $translateUrlLoader);
+
+function $translateUrlLoader($q, $http) {
+
+ 'use strict';
+
+ return function (options) {
+
+ if (!options || !options.url) {
+ throw new Error('Couldn\'t use urlLoader since no url is given!');
+ }
+
+ var requestParams = {};
+
+ requestParams[options.queryParameter || 'lang'] = options.key;
+
+ return $http(angular.extend({
+ url: options.url,
+ params: requestParams,
+ method: 'GET'
+ }, options.$http))
+ .then(function(result) {
+ return result.data;
+ }, function () {
+ return $q.reject(options.key);
+ });
+ };
+}
+
+$translateUrlLoader.displayName = '$translateUrlLoader';
+return 'pascalprecht.translate';
+
+}));
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate-loader-url/angular-translate-loader-url.min.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate-loader-url/angular-translate-loader-url.min.js
new file mode 100644
index 0000000000..3d955c755d
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate-loader-url/angular-translate-loader-url.min.js
@@ -0,0 +1,6 @@
+/*!
+ * angular-translate - v2.15.1 - 2017-03-04
+ *
+ * Copyright (c) 2017 The angular-translate team, Pascal Precht; Licensed MIT
+ */
+!function(a,b){"function"==typeof define&&define.amd?define([],function(){return b()}):"object"==typeof exports?module.exports=b():b()}(this,function(){function a(a,b){"use strict";return function(c){if(!c||!c.url)throw new Error("Couldn't use urlLoader since no url is given!");var d={};return d[c.queryParameter||"lang"]=c.key,b(angular.extend({url:c.url,params:d,method:"GET"},c.$http)).then(function(a){return a.data},function(){return a.reject(c.key)})}}return a.$inject=["$q","$http"],angular.module("pascalprecht.translate").factory("$translateUrlLoader",a),a.displayName="$translateUrlLoader","pascalprecht.translate"});
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate-loader-url/bower.json b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate-loader-url/bower.json
new file mode 100644
index 0000000000..7a20af9b0e
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate-loader-url/bower.json
@@ -0,0 +1,12 @@
+{
+ "name": "angular-translate-loader-url",
+ "description": "A plugin for Angular Translate",
+ "version": "2.15.1",
+ "main": "./angular-translate-loader-url.js",
+ "ignore": [],
+ "author": "Pascal Precht",
+ "license": "MIT",
+ "dependencies": {
+ "angular-translate": "~2.15.1"
+ }
+}
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate-loader-url/package.json b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate-loader-url/package.json
new file mode 100644
index 0000000000..693175e5b1
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate-loader-url/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "angular-translate-loader-url",
+ "version": "2.15.1",
+ "description": "Creates a loading function for a typical dynamic url pattern: \"locale.php?lang=en_US\", \"locale.php?lang=de_DE\", \"locale.php?language=nl_NL\" etc. Prefixing the specified url, the current requested, language id will be applied with \"?{queryParameter}={key}\". Using this service, the response of these urls must be an object of key-value pairs.",
+ "main": "angular-translate-loader-url.js",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/angular-translate/bower-angular-translate-loader-url.git"
+ },
+ "keywords": [
+ "angular",
+ "translate",
+ "loader"
+ ],
+ "author": "Pascal Precht",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/angular-translate/angular-translate/issues"
+ },
+ "homepage": "https://angular-translate.github.io",
+ "dependencies": {
+ "angular-translate": "~2.15.1"
+ }
+}
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/.npmignore b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/.npmignore
new file mode 100644
index 0000000000..a2b37487e8
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/.npmignore
@@ -0,0 +1,26 @@
+/bower_components/
+/build_tools/
+/demo/
+/docs/
+/identity/
+/src/
+/test/
+/test_scopes/
+/tmp
+/.bowerrc
+/.editorconfig
+/.jshintrc
+/.github
+/.travis.yml
+/bower.json
+/CONTRIBUTING.md
+/*.sh
+/*.js
+/*.png
+/build/
+
+# IntelliJ stuff
+.idea
+*.iml
+# Eclipse (plugins)
+/atlassian-ide-plugin.xml
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/.nvmrc b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/.nvmrc
new file mode 100644
index 0000000000..12e4141293
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/.nvmrc
@@ -0,0 +1 @@
+6.9
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/CHANGELOG.md b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/CHANGELOG.md
new file mode 100644
index 0000000000..bb2f665ee2
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/CHANGELOG.md
@@ -0,0 +1,916 @@
+
+## [2.15.1](https://github.com/angular-translate/angular-translate/compare/2.15.0...v2.15.1) (2017-03-04)
+
+
+### Bug Fixes
+
+* **cloak:** fix missing decloak introduced by optimize [#1694](https://github.com/angular-translate/angular-translate/issues/1694) ([a9ec123](https://github.com/angular-translate/angular-translate/commit/a9ec123)), closes [#1705](https://github.com/angular-translate/angular-translate/issues/1705)
+
+
+
+
+# [2.15.0](https://github.com/angular-translate/angular-translate/compare/2.14.0...v2.15.0) (2017-02-27)
+
+
+### Features
+
+* **cookies:** use $cookies (1.4+) or $cookieStore (<1.4) ([51330f5](https://github.com/angular-translate/angular-translate/commit/51330f5))
+* **filter:** ensure no this==undefined will be injected ([5cb94cb](https://github.com/angular-translate/angular-translate/commit/5cb94cb))
+
+
+
+
+# [2.14.0](https://github.com/angular-translate/angular-translate/compare/2.13.1...v2.14.0) (2017-02-11)
+
+
+### Bug Fixes
+
+* **$translate:** reassign language promises in refresh, update translation tables at the appropriate time, and simplify the routine ([351eb8f](https://github.com/angular-translate/angular-translate/commit/351eb8f))
+* **$translatePartialLoader:** prevent duplicate simultaneous HTTP requests ([8b2cea8](https://github.com/angular-translate/angular-translate/commit/8b2cea8))
+* **service:** add explicit promise rejection handler for $translate.use ([f4dc14a](https://github.com/angular-translate/angular-translate/commit/f4dc14a))
+* **service:** avoid sanitize/esape calls on null/undefined param values ([331e0dd](https://github.com/angular-translate/angular-translate/commit/331e0dd))
+* **service:** fix missing promise rejection handlers ([776993b](https://github.com/angular-translate/angular-translate/commit/776993b))
+* **staticFilesLoader:** do not use empty string as $http params ([ac2a038](https://github.com/angular-translate/angular-translate/commit/ac2a038)), closes [#1646](https://github.com/angular-translate/angular-translate/issues/1646)
+* **tests:** rewrite tests for AJS 1.6 compatibility ([7c9d2c9](https://github.com/angular-translate/angular-translate/commit/7c9d2c9))
+* **translate:** handle null translation ([1e57b4f](https://github.com/angular-translate/angular-translate/commit/1e57b4f)), closes [#665](https://github.com/angular-translate/angular-translate/issues/665)
+* **translateCloak:** incorrect element reference, inappropriate decloak at onReady, inappropriate decloak at $translateChangeSuccess ([a4d2795](https://github.com/angular-translate/angular-translate/commit/a4d2795))
+
+
+### Features
+
+* **dependencies:** update to messageformat 1.0.2 ([d4a0468](https://github.com/angular-translate/angular-translate/commit/d4a0468))
+* **service:** add translationId as param of custom interpolation service interface ([5de40de](https://github.com/angular-translate/angular-translate/commit/5de40de))
+* **tests:** add current AngularJS 1.6 in test scopes ([d8abdc5](https://github.com/angular-translate/angular-translate/commit/d8abdc5))
+
+
+
+
+## [2.13.1](https://github.com/angular-translate/angular-translate/compare/2.13.0...v2.13.1) (2016-12-06)
+
+
+
+
+# [2.13.0](https://github.com/angular-translate/angular-translate/compare/2.12.1...v2.13.0) (2016-10-30)
+
+
+### Bug Fixes
+
+* **service:** fix .instant() not handling TrustedValueHolderType correctly ([1ede55e](https://github.com/angular-translate/angular-translate/commit/1ede55e)), closes [#1618](https://github.com/angular-translate/angular-translate/issues/1618)
+* **service:** reject promise if handler returns undefined ([8fe6f23](https://github.com/angular-translate/angular-translate/commit/8fe6f23)), closes [#1600](https://github.com/angular-translate/angular-translate/issues/1600)
+* **service:** return empty string when found in fallback ([d76227e](https://github.com/angular-translate/angular-translate/commit/d76227e))
+
+
+### Features
+
+* **sanitize:** sanitize override on instant call ([01fecd0](https://github.com/angular-translate/angular-translate/commit/01fecd0))
+* **service:** add $translate.getTranslationTable(langKey) ([40f9e35](https://github.com/angular-translate/angular-translate/commit/40f9e35))
+* **service:** add file map lookup into static-files loader ([132e49a](https://github.com/angular-translate/angular-translate/commit/132e49a))
+* **service:** add mf configurer [#1619](https://github.com/angular-translate/angular-translate/issues/1619) ([676114b](https://github.com/angular-translate/angular-translate/commit/676114b))
+
+
+
+
+## [2.12.1](https://github.com/angular-translate/angular-translate/compare/2.12.0...v2.12.1) (2016-09-15)
+
+
+### Bug Fixes
+
+* **build:** Add missing translate-attr directive to Gruntfile.js ([e70e9ad](https://github.com/angular-translate/angular-translate/commit/e70e9ad)), closes [#1577](https://github.com/angular-translate/angular-translate/issues/1577)
+* **style:** fix code style issues in ~-attr directive ([1848bc8](https://github.com/angular-translate/angular-translate/commit/1848bc8))
+
+
+
+
+# [2.12.0](https://github.com/angular-translate/angular-translate/compare/2.11.1...v2.12.0) (2016-09-05)
+
+
+### Bug Fixes
+
+* **service:** fix infinite loop when fallback language async loading fails ([233f30c](https://github.com/angular-translate/angular-translate/commit/233f30c))
+* **service:** treat date param as-is (no sanitize/escape) ([ab1ecce](https://github.com/angular-translate/angular-translate/commit/ab1ecce)), closes [#1560](https://github.com/angular-translate/angular-translate/issues/1560)
+
+
+### Features
+
+* **directive:** introduce standalone translate-attr directive ([bcb0f2c](https://github.com/angular-translate/angular-translate/commit/bcb0f2c))
+* **partial loader:** add error response to errorHandler ([e3aba1c](https://github.com/angular-translate/angular-translate/commit/e3aba1c))
+* **service:** introduce new sanitize strategies: sce/sceParameters ([1624df5](https://github.com/angular-translate/angular-translate/commit/1624df5))
+* **service:** provide for sanitize/escape strategy 3rd argument context ([8504c60](https://github.com/angular-translate/angular-translate/commit/8504c60))
+
+
+
+
+## [2.11.1](https://github.com/angular-translate/angular-translate/compare/2.11.0...v2.11.1) (2016-07-17)
+
+
+### Bug Fixes
+
+* **dependencies:** Update messageformat to ~0.3.1 ([04e11c9](https://github.com/angular-translate/angular-translate/commit/04e11c9))
+* **grunt:** add work-around for uglify preserveComments as expected ([32cdedb](https://github.com/angular-translate/angular-translate/commit/32cdedb)), closes [#1461](https://github.com/angular-translate/angular-translate/issues/1461)
+* **service:** allow instant function to also take care of post process configuration ([b7d7907](https://github.com/angular-translate/angular-translate/commit/b7d7907))
+* **service:** avoid sanitizing of functions ([492d8e5](https://github.com/angular-translate/angular-translate/commit/492d8e5)), closes [#1529](https://github.com/angular-translate/angular-translate/issues/1529)
+* **service:** Correct descriptive ngdocs to match parameters on the service calls ([91711f7](https://github.com/angular-translate/angular-translate/commit/91711f7))
+* **service:** fix interpolation issue with non-string as input ([fa4a80e](https://github.com/angular-translate/angular-translate/commit/fa4a80e)), closes [#1511](https://github.com/angular-translate/angular-translate/issues/1511)
+* **service:** fix lost of data in async loader / error in runtime ([5ee0c3e](https://github.com/angular-translate/angular-translate/commit/5ee0c3e))
+
+
+### Features
+
+* **directive:** introduce a global keepContent setting ([2015f79](https://github.com/angular-translate/angular-translate/commit/2015f79))
+
+
+
+
+# [2.11.0](https://github.com/angular-translate/angular-translate/compare/2.10.0...v2.11.0) (2016-03-20)
+
+
+### Bug Fixes
+
+* **directive:** reduced number of watchers by applying translateLanguage watcher only when direc ([961fc92](https://github.com/angular-translate/angular-translate/commit/961fc92))
+* **service:** add missing hasOwnProperty check ([823afc0](https://github.com/angular-translate/angular-translate/commit/823afc0))
+* **service:** avoid try to load languages which are explicitly not wanted ([bde935e](https://github.com/angular-translate/angular-translate/commit/bde935e)), closes [#1390](https://github.com/angular-translate/angular-translate/issues/1390)
+* **service:** fix edge-case with .use() and .preferredLanguage() ([02688f2](https://github.com/angular-translate/angular-translate/commit/02688f2))
+* **service:** translations for `forceLanguage` will be loaded on demand ([14bc956](https://github.com/angular-translate/angular-translate/commit/14bc956)), closes [#1389](https://github.com/angular-translate/angular-translate/issues/1389)
+
+### Features
+
+* **depenceny:** Update messageformat.js to current 0.3.0 release ([fb48f78](https://github.com/angular-translate/angular-translate/commit/fb48f78))
+* **directive:** introduce attr translate-keep-content ([b2cf8a3](https://github.com/angular-translate/angular-translate/commit/b2cf8a3))
+* **service:** add `$translate.resolveClientLocale()` (also at provider) ([d0469ac](https://github.com/angular-translate/angular-translate/commit/d0469ac))
+* **service:** add support for uniformLanguageTag('iso639-1') ([1e037ec](https://github.com/angular-translate/angular-translate/commit/1e037ec)), closes [#1181](https://github.com/angular-translate/angular-translate/issues/1181)
+* **service:** improve messageformat.js output caching ([cb31608](https://github.com/angular-translate/angular-translate/commit/cb31608))
+* **service:** introduce getter returning available languages ([3988af0](https://github.com/angular-translate/angular-translate/commit/3988af0)), closes [#1304](https://github.com/angular-translate/angular-translate/issues/1304)
+* **service:** introduce post processing for translations ([f0c4874](https://github.com/angular-translate/angular-translate/commit/f0c4874))
+* **service:** support for default translation in missingTranslationHandler ([8c5044c](https://github.com/angular-translate/angular-translate/commit/8c5044c))
+
+
+
+
+# [2.10.0](https://github.com/angular-translate/angular-translate/compare/2.9.2...v2.10.0) (2016-02-28)
+
+
+### Bug Fixes
+
+* **service:** make the fallback $uses / $translate.use work in a correct manner ([7e71a5a](https://github.com/angular-translate/angular-translate/commit/7e71a5a))
+
+
+
+
+## [2.9.2](https://github.com/angular-translate/angular-translate/compare/2.9.1...v2.9.2) (2016-02-21)
+
+
+### Bug Fixes
+
+* **package:** redefine dependency version range (AJS 1.5) ([94eb844](https://github.com/angular-translate/angular-translate/commit/94eb844)), closes [#1394](https://github.com/angular-translate/angular-translate/issues/1394) [#1395](https://github.com/angular-translate/angular-translate/issues/1395) [#1397](https://github.com/angular-translate/angular-translate/issues/1397)
+* **package:** redefine dependency version range (AJS 1.5) (fixup) ([20da73d](https://github.com/angular-translate/angular-translate/commit/20da73d)), closes [#1394](https://github.com/angular-translate/angular-translate/issues/1394) [#1395](https://github.com/angular-translate/angular-translate/issues/1395) [#1397](https://github.com/angular-translate/angular-translate/issues/1397)
+* **service:** avoid call stack size error, print proper message ([73ea6e3](https://github.com/angular-translate/angular-translate/commit/73ea6e3))
+* **service:** ensure fallback language can be selected as `$uses` ([40ad523](https://github.com/angular-translate/angular-translate/commit/40ad523))
+* **service:** remove invalid argument for promise.finally ([2d72908](https://github.com/angular-translate/angular-translate/commit/2d72908))
+
+
+
+
+## [2.9.1](https://github.com/angular-translate/angular-translate/compare/2.9.0...v2.9.1) (2016-02-13)
+
+
+### Bug Fixes
+
+* **package:** redefine dependency version range (AJS 1.5) ([9ccce6b](https://github.com/angular-translate/angular-translate/commit/9ccce6b)), closes [#1394](https://github.com/angular-translate/angular-translate/issues/1394) [#1395](https://github.com/angular-translate/angular-translate/issues/1395) [#1397](https://github.com/angular-translate/angular-translate/issues/1397)
+
+
+
+
+# [2.9.0](https://github.com/angular-translate/angular-translate/compare/2.8.1...v2.9.0) (2016-01-24)
+
+
+### Bug Fixes
+
+* **$translate:** apply notFoundIndicators only when all configured language checked in $translate ([25b13c4](https://github.com/angular-translate/angular-translate/commit/25b13c4)), closes [#1314](https://github.com/angular-translate/angular-translate/issues/1314)
+* **directive:** add additional watcher validating cloak ([e7536b5](https://github.com/angular-translate/angular-translate/commit/e7536b5)), closes [#1287](https://github.com/angular-translate/angular-translate/issues/1287)
+* **directive:** enforce update on default text change only ([ea94acd](https://github.com/angular-translate/angular-translate/commit/ea94acd))
+* **docs:** correct all occurrences of language names PR #1243 ([5f89d55](https://github.com/angular-translate/angular-translate/commit/5f89d55))
+* **docs:** fix broken link ([e641fe4](https://github.com/angular-translate/angular-translate/commit/e641fe4))
+* **docs:** Fix some typos in spanish ([830a84b](https://github.com/angular-translate/angular-translate/commit/830a84b))
+* **docs:** refresh outdated link ([392cab0](https://github.com/angular-translate/angular-translate/commit/392cab0))
+* **package:** add missing run-scriptlet "clean-test-scopes" ([c22c727](https://github.com/angular-translate/angular-translate/commit/c22c727))
+* **service:** partial loader service refetches list of parts ([069eafd](https://github.com/angular-translate/angular-translate/commit/069eafd)), closes [#1326](https://github.com/angular-translate/angular-translate/issues/1326)
+
+### Features
+
+* **build:** update test scope "AJS 1.5" using rc0 ([26cdc05](https://github.com/angular-translate/angular-translate/commit/26cdc05))
+* **dependencies:** add `angular` as the required dependency ([475a9b6](https://github.com/angular-translate/angular-translate/commit/475a9b6))
+* **service:** expose `$translate.negotiateLocale` being public ([9247000](https://github.com/angular-translate/angular-translate/commit/9247000))
+* **service:** force language used for translating ([e591462](https://github.com/angular-translate/angular-translate/commit/e591462))
+
+
+
+
+## [2.8.1](https://github.com/angular-translate/angular-translate/compare/2.8.0...v2.8.1) (2015-10-01)
+
+
+### Bug Fixes
+
+* **service:** Fix `$translate.isReady()` won't return true if ready ([b40a344](https://github.com/angular-translate/angular-translate/commit/b40a344)), closes [#1239](https://github.com/angular-translate/angular-translate/issues/1239)
+* **service:** should not abort fallback languages (feature #1070) ([cc410b1](https://github.com/angular-translate/angular-translate/commit/cc410b1)), closes [#1070](https://github.com/angular-translate/angular-translate/issues/1070)
+
+
+
+
+# [2.8.0](https://github.com/angular-translate/angular-translate/compare/2.7.2...2.8.0) (2015-09-18)
+
+
+### Bug Fixes
+
+* **build:** ensure MessageFormat will be added correctly when using UMD ([f5e039c](https://github.com/angular-translate/angular-translate/commit/f5e039c))
+* **directive:** Fix behavior of translate-cloak timing ([a6adf47](https://github.com/angular-translate/angular-translate/commit/a6adf47)), closes [#929](https://github.com/angular-translate/angular-translate/issues/929) [#1175](https://github.com/angular-translate/angular-translate/issues/1175)
+* **directive:** Fix special IE11 issue #925 ([c4b16d3](https://github.com/angular-translate/angular-translate/commit/c4b16d3)), closes [#925](https://github.com/angular-translate/angular-translate/issues/925)
+* **docs:** avoid using absolute links in lang chooser #1136 ([2cdc902](https://github.com/angular-translate/angular-translate/commit/2cdc902))
+* **docs:** Fix more typos in CONTRIBUTING.md, add some infos about tests ([e88b990](https://github.com/angular-translate/angular-translate/commit/e88b990))
+* **docs:** Fix typo in CONTRIBUTING.md ([1c2ac47](https://github.com/angular-translate/angular-translate/commit/1c2ac47))
+* **docs:** Fix typo in zh-cn docs ([2a16eb6](https://github.com/angular-translate/angular-translate/commit/2a16eb6))
+* **service:** abort the last loader if not finished #1070 ([dd4a8b4](https://github.com/angular-translate/angular-translate/commit/dd4a8b4))
+* **service:** update storage before triggering $translateChangeSuccess ([77dd5a2](https://github.com/angular-translate/angular-translate/commit/77dd5a2))
+* **service provider:** change/fix return of preferredLanguage() ([6014a81](https://github.com/angular-translate/angular-translate/commit/6014a81))
+
+### Features
+
+* **directive:** translate-namespace directive ([45523bb](https://github.com/angular-translate/angular-translate/commit/45523bb))
+* **loaders:** addition to e7516dc #1080 (disable legacy $http cbs) ([233a012](https://github.com/angular-translate/angular-translate/commit/233a012))
+* **loaders:** remove use of legacy methods on $http promises #1080 ([e7516dc](https://github.com/angular-translate/angular-translate/commit/e7516dc))
+* **meta:** enrich copyright header with a leagl person ([21da61c](https://github.com/angular-translate/angular-translate/commit/21da61c))
+* **sanitize:** Allow sanitize strategy defined as a service ([8a6cc07](https://github.com/angular-translate/angular-translate/commit/8a6cc07))
+* **service:** add option to customize the nested delimiter ([78161f8](https://github.com/angular-translate/angular-translate/commit/78161f8))
+* **service:** introduce `isReady()` and `onReady()` with event ([9a4bd0d](https://github.com/angular-translate/angular-translate/commit/9a4bd0d))
+
+
+
+
+## [2.7.2](https://github.com/angular-translate/angular-translate/compare/2.7.1...2.7.2) (2015-06-01)
+
+
+### Bug Fixes
+
+* **directive:** ensure value of `translate` will be translated always ([454d702](https://github.com/angular-translate/angular-translate/commit/454d702))
+* **sanitization:** fix/workaround issue when jQuery is not available ([ef1b10a](https://github.com/angular-translate/angular-translate/commit/ef1b10a))
+* **service:** fix silence on error, add missing catch on `refresh()` ([f3ec956](https://github.com/angular-translate/angular-translate/commit/f3ec956))
+* **service:** fix silence on error, add missing catch on `refresh()` ([5a85a64](https://github.com/angular-translate/angular-translate/commit/5a85a64))
+* **service:** make provider's storageKey chainable ([de8c253](https://github.com/angular-translate/angular-translate/commit/de8c253))
+
+
+
+
+## [2.7.1](https://github.com/angular-translate/angular-translate/compare/2.7.0...2.7.1) (2015-06-01)
+
+
+### Bug Fixes
+
+* **docs:** fix typo in $translateChangeSuccess ([89e2569](https://github.com/angular-translate/angular-translate/commit/89e2569))
+* **service:** handle error "this.replace is not a function" ([8616dca](https://github.com/angular-translate/angular-translate/commit/8616dca))
+* **service:** integrate translationCache into service distribution file ([2fcbc60](https://github.com/angular-translate/angular-translate/commit/2fcbc60))
+
+### Features
+
+* **$translateProvider:** add a new option to force async reload ([bdee77f](https://github.com/angular-translate/angular-translate/commit/bdee77f))
+
+
+
+
+# [2.7.0](https://github.com/angular-translate/angular-translate/compare/2.6.1...2.7.0) (2015-05-02)
+
+
+### Bug Fixes
+
+* **directive:** fix issue with `data-` prefixed attributes #954 ([ee253bc](https://github.com/angular-translate/angular-translate/commit/ee253bc)), closes [#954](https://github.com/angular-translate/angular-translate/issues/954)
+* **directive:** fix translate-value-* weren't be available on init ([98e8279](https://github.com/angular-translate/angular-translate/commit/98e8279))
+* **directive:** fix wrong initial translation causing overloading ([657ed8a](https://github.com/angular-translate/angular-translate/commit/657ed8a))
+* **directive:** handle interpolation of undefined keys correctly in updateTranslations, fixes is ([3f7cf4c](https://github.com/angular-translate/angular-translate/commit/3f7cf4c)), closes [#971](https://github.com/angular-translate/angular-translate/issues/971)
+* **directive:** Make interpolate message format work smoothly also on message format > 0.1.7 - f ([2533f2d](https://github.com/angular-translate/angular-translate/commit/2533f2d)), closes [#789](https://github.com/angular-translate/angular-translate/issues/789)
+* **directive:** make translate-values interpolate correctly with newer MessageFormat.js ([887dc1b](https://github.com/angular-translate/angular-translate/commit/887dc1b))
+* **docs:** bug in "Flash of untranslated content" section ([af5d746](https://github.com/angular-translate/angular-translate/commit/af5d746))
+* **docs:** fix invalid link in directive ([985cfd5](https://github.com/angular-translate/angular-translate/commit/985cfd5))
+* **docs:** typo in module type ([f0527b1](https://github.com/angular-translate/angular-translate/commit/f0527b1))
+* **feat:** export module name improving usage module loaders #944 ([cb33f63](https://github.com/angular-translate/angular-translate/commit/cb33f63))
+* **messageformat:** add duck type check for numbers #789 ([bbc1cbe](https://github.com/angular-translate/angular-translate/commit/bbc1cbe))
+* **refresh:** it has to clear all tables if no language key is specified ([3cce795](https://github.com/angular-translate/angular-translate/commit/3cce795))
+* **service:** always remove stored ref for lang promises ([dbd5be9](https://github.com/angular-translate/angular-translate/commit/dbd5be9)), closes [#824](https://github.com/angular-translate/angular-translate/issues/824) [#969](https://github.com/angular-translate/angular-translate/issues/969)
+* **service:** do not try to load a predefined fallback language ([3be14df](https://github.com/angular-translate/angular-translate/commit/3be14df))
+* **service:** fix an issue resolving after missing translations ([a13899f](https://github.com/angular-translate/angular-translate/commit/a13899f))
+* **service:** fix possible npe ([1aaab98](https://github.com/angular-translate/angular-translate/commit/1aaab98))
+* **test/refresh:** fix current table refreshing test ([a298ed8](https://github.com/angular-translate/angular-translate/commit/a298ed8))
+
+### Features
+
+* **$translatePartialLoader:** accept function in urlTemplate ([401204a](https://github.com/angular-translate/angular-translate/commit/401204a))
+* **build:** introduce module definition ([00b73ff](https://github.com/angular-translate/angular-translate/commit/00b73ff))
+* **filter:** add new option `$translate.statefulFilter()` ([dec4bf3](https://github.com/angular-translate/angular-translate/commit/dec4bf3))
+* **missingTranslationHandlerFactory:** pass interpolationParams to missingTranslationHandlerFactory ([a361fd0](https://github.com/angular-translate/angular-translate/commit/a361fd0))
+* **sanitization:** refactored, fixed and extended sanitization #993 ([12dbc57](https://github.com/angular-translate/angular-translate/commit/12dbc57)), closes [#993](https://github.com/angular-translate/angular-translate/issues/993)
+* **service:** add uniformLanguageTagResolver ([b534e1a](https://github.com/angular-translate/angular-translate/commit/b534e1a))
+
+### Performance Improvements
+
+* **directive:** watch parameters only if exist ([f0e2585](https://github.com/angular-translate/angular-translate/commit/f0e2585))
+
+
+### BREAKING CHANGES
+
+* You will get a warning message when using the default setting (not escaping the content).
+You can fix (and remove) this warning by explicit set a sanitization strategy
+within your config phase configuring $translateProvider. Even configuring the `null` mode will let the
+warning disapper. You are highly encouraged specifing any mode except `null` because of security concerns.
+
+
+
+## [2.6.1](https://github.com/angular-translate/angular-translate/compare/2.6.0...2.6.1) (2015-03-01)
+
+
+### Bug Fixes
+
+* **bower spec:** fix bower main property #922 ([3a1ad10](https://github.com/angular-translate/angular-translate/commit/3a1ad10)), closes [#922](https://github.com/angular-translate/angular-translate/issues/922)
+* **custom interpolator:** improve handling of interpolator ids which don't exist ([373b46f](https://github.com/angular-translate/angular-translate/commit/373b46f))
+* **static-files-loader:** fix multiple files definition (docu update) #923, pr #936 ([e637c01](https://github.com/angular-translate/angular-translate/commit/e637c01)), closes [#923](https://github.com/angular-translate/angular-translate/issues/923) [#936](https://github.com/angular-translate/angular-translate/issues/936)
+* **static-files-loader:** fix multiple files definition #923 ([1b6256a](https://github.com/angular-translate/angular-translate/commit/1b6256a)), closes [#923](https://github.com/angular-translate/angular-translate/issues/923)
+
+
+
+
+# [2.6.0](https://github.com/angular-translate/angular-translate/compare/2.5.2...2.6.0) (2015-02-08)
+
+
+### Bug Fixes
+
+* **directive:** ensure internal watcher will be removed ([e69f4a1](https://github.com/angular-translate/angular-translate/commit/e69f4a1))
+* **directive:** fix minor memory leak ([5e4533a](https://github.com/angular-translate/angular-translate/commit/5e4533a))
+* **directive:** fix missing update using dynamic translationIds ([faebe19](https://github.com/angular-translate/angular-translate/commit/faebe19)), closes [#854](https://github.com/angular-translate/angular-translate/issues/854)
+* **directive:** newlines before/after translation ids should be ignored ([8dcf3e2](https://github.com/angular-translate/angular-translate/commit/8dcf3e2)), closes [#909](https://github.com/angular-translate/angular-translate/issues/909)
+* **directive, service:** return value of translate-default also in case fallback languages are used - rel ([fcd6b3e](https://github.com/angular-translate/angular-translate/commit/fcd6b3e))
+* **filter:** apply notFoundIndicators also for instant translations correctly ([5a9f436](https://github.com/angular-translate/angular-translate/commit/5a9f436)), closes [#866](https://github.com/angular-translate/angular-translate/issues/866)
+* **service:** fallback languages follow shortcuts (fixes #758) ([cce897a](https://github.com/angular-translate/angular-translate/commit/cce897a)), closes [#758](https://github.com/angular-translate/angular-translate/issues/758)
+* **service:** fix an issue with default interpolator and expressions ([75b7381](https://github.com/angular-translate/angular-translate/commit/75b7381))
+* **service:** use $window/$windowProvider instead of window ([bfa7b7b](https://github.com/angular-translate/angular-translate/commit/bfa7b7b))
+
+### Features
+
+* **$translatePartialLoader:** adds optional priority param to the addPart function ([570617c](https://github.com/angular-translate/angular-translate/commit/570617c))
+* **directive:** add $translateProvider.directityPriority ([b0b7716](https://github.com/angular-translate/angular-translate/commit/b0b7716))
+* **loader:** support for multiple static translation files ([c462ee6](https://github.com/angular-translate/angular-translate/commit/c462ee6))
+* **service:** extend loader api: add isPartLoaded and getRegisteredParts to $translatePartialL ([54f8ab3](https://github.com/angular-translate/angular-translate/commit/54f8ab3))
+
+
+
+
+## [2.5.2](https://github.com/angular-translate/angular-translate/compare/2.5.0...2.5.2) (2014-12-10)
+
+
+### Bug Fixes
+
+* **directive:** missing watch for expression within elements text nodes ([31c0356](https://github.com/angular-translate/angular-translate/commit/31c0356)), closes [#701](https://github.com/angular-translate/angular-translate/issues/701)
+
+
+
+
+# [2.5.0](https://github.com/angular-translate/angular-translate/compare/2.4.2...2.5.0) (2014-12-07)
+
+
+### Bug Fixes
+
+* **directive:** ensure directive's text will be parsed at least once ([49cfef0](https://github.com/angular-translate/angular-translate/commit/49cfef0))
+* **loader:** under circum understances translation table got lost ([df37381](https://github.com/angular-translate/angular-translate/commit/df37381))
+* **messageformat-interpolation:** fix support for messageformat 0.2.* ([ac8d5ed](https://github.com/angular-translate/angular-translate/commit/ac8d5ed))
+* **service:** apply fix for empty strings in `navigator.language` ([5b4edd9](https://github.com/angular-translate/angular-translate/commit/5b4edd9))
+* **service:** fix npe when resolving fallback language for `instant` ([7c09d89](https://github.com/angular-translate/angular-translate/commit/7c09d89))
+
+### Features
+
+* **$translateUrlLoader:** allow to use custom query parameter name for url loader ([e360bf8](https://github.com/angular-translate/angular-translate/commit/e360bf8))
+* **module:** use same fallback for module.run when no storage key is set ([247253d](https://github.com/angular-translate/angular-translate/commit/247253d)), closes [#739](https://github.com/angular-translate/angular-translate/issues/739)
+* **storage:** rename set() into put() ([ef6a613](https://github.com/angular-translate/angular-translate/commit/ef6a613))
+
+
+### BREAKING CHANGES
+
+* This marks storage.set() as deprecated. In the
+next major release v3, the old method `set()` will be dropped in favor
+of `put()`.
+Relates #772
+
+
+
+## [2.4.2](https://github.com/angular-translate/angular-translate/compare/2.4.1...2.4.2) (2014-10-21)
+
+
+### Bug Fixes
+
+* **partialloader:** fix possible circular dependency ([25f252c](https://github.com/angular-translate/angular-translate/commit/25f252c)), closes [#766](https://github.com/angular-translate/angular-translate/issues/766)
+
+### Features
+
+* **directive:** translate attributes (optimize process flow) ([508fd32](https://github.com/angular-translate/angular-translate/commit/508fd32))
+* **directive:** translate attributes using directive ([1d06d2a](https://github.com/angular-translate/angular-translate/commit/1d06d2a)), closes [#568](https://github.com/angular-translate/angular-translate/issues/568)
+* **directive:** translate-cloak supports optional value for cloaking ([f7ccb7f](https://github.com/angular-translate/angular-translate/commit/f7ccb7f))
+
+
+
+
+## [2.4.1](https://github.com/angular-translate/angular-translate/compare/2.4.0...2.4.1) (2014-10-03)
+
+
+### Bug Fixes
+
+* **service:** add missing final event on new (async) translations ([22cc8b4](https://github.com/angular-translate/angular-translate/commit/22cc8b4))
+* **service:** constructor `useUrlLoader()` missed optional options ([22f5c4b](https://github.com/angular-translate/angular-translate/commit/22f5c4b))
+* **service, loaders:** the loader options ($http) have been merged wrong ([0c35a95](https://github.com/angular-translate/angular-translate/commit/0c35a95)), closes [#754](https://github.com/angular-translate/angular-translate/issues/754) [#547](https://github.com/angular-translate/angular-translate/issues/547)
+
+
+
+
+# [2.4.0](https://github.com/angular-translate/angular-translate/compare/2.3.0...2.4.0) (2014-09-22)
+
+
+### Bug Fixes
+
+* **filter:** interpolated params w/ scope aren't possible starting AJS1.3 ([9465318](https://github.com/angular-translate/angular-translate/commit/9465318))
+* **filter:** mark filter being stateful required since Angular 1.3 rc2 ([bffbf04](https://github.com/angular-translate/angular-translate/commit/bffbf04))
+* **service:** `$nextLang` should be not unset parallel loadings ([d1745e4](https://github.com/angular-translate/angular-translate/commit/d1745e4)), closes [#647](https://github.com/angular-translate/angular-translate/issues/647)
+* **service:** avoid possible doubled requested on refresh() ([98d429d](https://github.com/angular-translate/angular-translate/commit/98d429d))
+* **service:** avoid possible npe in internal getTranslationTable() ([9aaa9a0](https://github.com/angular-translate/angular-translate/commit/9aaa9a0))
+* **service:** correctly iterate in fallback languages (fixes #690) ([ac2f35c](https://github.com/angular-translate/angular-translate/commit/ac2f35c)), closes [#690](https://github.com/angular-translate/angular-translate/issues/690)
+
+### Features
+
+* **loader:** apply support for loaderOptions.$http ([8613bef](https://github.com/angular-translate/angular-translate/commit/8613bef))
+* **loaders:** introduce loader cache ([b685601](https://github.com/angular-translate/angular-translate/commit/b685601)), closes [#529](https://github.com/angular-translate/angular-translate/issues/529)
+* **service:** enrich events with the currently handled language key ([73b289d](https://github.com/angular-translate/angular-translate/commit/73b289d))
+* **service:** interpolate translationId in case of rejected translation ([3efaac5](https://github.com/angular-translate/angular-translate/commit/3efaac5)), closes [#730](https://github.com/angular-translate/angular-translate/issues/730)
+* **service:** introduce `versionInfo` function ([e37d89c](https://github.com/angular-translate/angular-translate/commit/e37d89c))
+* **service:** prefer detecting language by `navigator.languages` #722 ([2204f4f](https://github.com/angular-translate/angular-translate/commit/2204f4f))
+
+
+### BREAKING CHANGES
+
+* Since filters are stateless and have no access to its scope anymore (see https://github.com/angular/angular.js/commit/8863b9d04c722b278fa93c5d66ad1e578ad6eb1f), a context must be given explicitly. This removes the feature of an interpolation based on the scope (context), even without the $rootScope.
+However, the feature will still work in AJS <=1.2, so we won't remove it completely yet. Handle the feature as slightly deprecated.
+
+
+
+# [2.3.0](https://github.com/angular-translate/angular-translate/compare/2.2.0...2.3.0) (2014-09-16)
+
+
+### Bug Fixes
+
+* **$translate:** return $missingTranslationHandler result when no translation was found ([7625951](https://github.com/angular-translate/angular-translate/commit/7625951))
+* **bower.json:** Avoid 'invalid-meta angular-bootstrap-affix is missing "ignore" entry in bower.j ([595501a](https://github.com/angular-translate/angular-translate/commit/595501a)), closes [bower/bower#1388](https://github.com/bower/bower/issues/1388)
+* **demo:** fixes wrong method call in demo ([47fc943](https://github.com/angular-translate/angular-translate/commit/47fc943))
+* **directive:** change event for listening to `$translateChangeEnd` ([98fe649](https://github.com/angular-translate/angular-translate/commit/98fe649)), closes [#658](https://github.com/angular-translate/angular-translate/issues/658)
+* **directive:** improve the cloak-directive's performance ([acab18a](https://github.com/angular-translate/angular-translate/commit/acab18a))
+* **docs:** fix example in directive ngdoc-documentation (fixes #678) ([176b3e9](https://github.com/angular-translate/angular-translate/commit/176b3e9)), closes [#678](https://github.com/angular-translate/angular-translate/issues/678)
+* **docs:** Fix typo ([6c2ab30](https://github.com/angular-translate/angular-translate/commit/6c2ab30))
+* **package.json:** remove unnecessary relative paths from package.json ([8e5b87e](https://github.com/angular-translate/angular-translate/commit/8e5b87e))
+* **service:** add shim for indexOf and trim #638 ([b951fd5](https://github.com/angular-translate/angular-translate/commit/b951fd5))
+* **service:** addition of preferred language to fallback language stack is now preventing dupl ([b2bb166](https://github.com/angular-translate/angular-translate/commit/b2bb166))
+* **service:** load fallback languages also for instant and filter ([ed6023a](https://github.com/angular-translate/angular-translate/commit/ed6023a))
+* **service:** use hasOwnProperty of prototype #638 ([d8a5060](https://github.com/angular-translate/angular-translate/commit/d8a5060))
+* **storage:** fix 'DOM Exception 18' at feature detection ([75504cb](https://github.com/angular-translate/angular-translate/commit/75504cb)), closes [#629](https://github.com/angular-translate/angular-translate/issues/629)
+* **storage:** fixup 75504cbe ([53a8bad](https://github.com/angular-translate/angular-translate/commit/53a8bad))
+* **translateService:** fixup/rewrite for b48f6bb (specs) ([45ac14d](https://github.com/angular-translate/angular-translate/commit/45ac14d))
+* **translateService:** prevent multiple XHR calls ([b48f6bb](https://github.com/angular-translate/angular-translate/commit/b48f6bb))
+
+### Features
+
+* **directive:** add possibility to mix translation interpolation with other text in element body ([be62131](https://github.com/angular-translate/angular-translate/commit/be62131)), closes [#461](https://github.com/angular-translate/angular-translate/issues/461)
+
+
+
+
+# [2.2.0](https://github.com/angular-translate/angular-translate/compare/2.1.0...2.2.0) (2014-06-03)
+
+
+### Bug Fixes
+
+* **$translate:** checks modification ([b91e4de](https://github.com/angular-translate/angular-translate/commit/b91e4de))
+* **$translate:** if translation exists, use the translated string even if it's empty ([4ba736f](https://github.com/angular-translate/angular-translate/commit/4ba736f))
+* **$translate:** if translation exists, use the translated string even if it's empty ([eeb8c2a](https://github.com/angular-translate/angular-translate/commit/eeb8c2a))
+* **$translate:** use case-insensitive check for language key aliases ([09a8bf1](https://github.com/angular-translate/angular-translate/commit/09a8bf1)), closes [#431](https://github.com/angular-translate/angular-translate/issues/431)
+* **$translate:** use case-insensitive check for language key aliases ([26ec308](https://github.com/angular-translate/angular-translate/commit/26ec308)), closes [#431](https://github.com/angular-translate/angular-translate/issues/431)
+* **$translateProvider:** determinePreferredLanguage was not chainable ([7c29f2f](https://github.com/angular-translate/angular-translate/commit/7c29f2f)), closes [#487](https://github.com/angular-translate/angular-translate/issues/487)
+* **$translateProvider:** fix comparison in one case of negotiateLocale() ([c2b94ca](https://github.com/angular-translate/angular-translate/commit/c2b94ca))
+* **$translateProvider:** fix comparison in one case of negotiateLocale() ([fe04c72](https://github.com/angular-translate/angular-translate/commit/fe04c72))
+* **demo:** correct demo of `translate-values` ([efa74fa](https://github.com/angular-translate/angular-translate/commit/efa74fa))
+* **demo:** correct demo of `translate-values` ([7de2ae2](https://github.com/angular-translate/angular-translate/commit/7de2ae2))
+* **demo:** use `.instant()` ([6bea192](https://github.com/angular-translate/angular-translate/commit/6bea192))
+* **directive:** Make translate-value-* work inside ng-if and ng-repeat ([e07eea7](https://github.com/angular-translate/angular-translate/commit/e07eea7)), closes [#433](https://github.com/angular-translate/angular-translate/issues/433)
+* **directive:** Make translate-value-* work inside ng-if and ng-repeat ([f22624b](https://github.com/angular-translate/angular-translate/commit/f22624b)), closes [#433](https://github.com/angular-translate/angular-translate/issues/433)
+* **docs:** removes explicit protocol declaration for assets ([eaa9bf7](https://github.com/angular-translate/angular-translate/commit/eaa9bf7)), closes [#513](https://github.com/angular-translate/angular-translate/issues/513)
+* **gruntfile:** fix image link ([65fc8be](https://github.com/angular-translate/angular-translate/commit/65fc8be))
+* **package.json:** fix repository url ([40af7ce](https://github.com/angular-translate/angular-translate/commit/40af7ce))
+* **package.json:** fix repository url ([a410c9a](https://github.com/angular-translate/angular-translate/commit/a410c9a))
+* **partialLoader:** fixes deprecated usage of arguments.callee ([1ac3a0a](https://github.com/angular-translate/angular-translate/commit/1ac3a0a))
+* **service:** docs annotation ([8ef0415](https://github.com/angular-translate/angular-translate/commit/8ef0415))
+* **service:** docs annotation ([839c4e8](https://github.com/angular-translate/angular-translate/commit/839c4e8))
+* **service:** use the aliased language key if available ([675e9a2](https://github.com/angular-translate/angular-translate/commit/675e9a2)), closes [#530](https://github.com/angular-translate/angular-translate/issues/530)
+* **storageLocal:** fixes QUOTAEXCEEDEDERROR (safari private browsing) ([59aa2a0](https://github.com/angular-translate/angular-translate/commit/59aa2a0))
+* fix npe on empty strings (trim()) ([c69de7b](https://github.com/angular-translate/angular-translate/commit/c69de7b))
+* **translateInterpolator:** make it work with 1.3-beta ([97e2241](https://github.com/angular-translate/angular-translate/commit/97e2241))
+
+### Features
+
+* **directive:** add option to define a default translation text ([a802665](https://github.com/angular-translate/angular-translate/commit/a802665))
+* **directive:** add option to define a default translation text ([fc57d26](https://github.com/angular-translate/angular-translate/commit/fc57d26))
+* **directive:** Support for camel casing interpolation variables. ([b345041](https://github.com/angular-translate/angular-translate/commit/b345041))
+* **directive:** Support for camel casing interpolation variables. ([4791e25](https://github.com/angular-translate/angular-translate/commit/4791e25))
+* **messageformat-support:** enhancing for sanitization like default ([ad01686](https://github.com/angular-translate/angular-translate/commit/ad01686))
+* **missingFallbackDefaultText:** enables a feature to return a default text for displaying in case of missing tra ([f24b15e](https://github.com/angular-translate/angular-translate/commit/f24b15e))
+* **service:** add possibility to translate a set of translation ids ([612dc27](https://github.com/angular-translate/angular-translate/commit/612dc27))
+* **service:** add possibility to translate a set of translation ids ([57bd07c](https://github.com/angular-translate/angular-translate/commit/57bd07c))
+* **service:** allow using wildcards in language aliases ([6f0ae3b](https://github.com/angular-translate/angular-translate/commit/6f0ae3b)), closes [#426](https://github.com/angular-translate/angular-translate/issues/426)
+
+
+
+
+## [2.0.1](https://github.com/angular-translate/angular-translate/compare/2.0.0...2.0.1) (2014-02-25)
+
+
+### Bug Fixes
+
+* **$translate:** Ensuring that languages will be set based on the order they are requested, not t ([c909cd2](https://github.com/angular-translate/angular-translate/commit/c909cd2))
+* **$translate:** Ensuring that languages will be set based on the order they are requested, not t ([ebd62af](https://github.com/angular-translate/angular-translate/commit/ebd62af))
+* **$translate:** Ensuring that languages will be set based on the order they are requested, not t ([32e1851](https://github.com/angular-translate/angular-translate/commit/32e1851))
+* **instant:** $translate.instant(id) does not return correct fallback ([eec1d77](https://github.com/angular-translate/angular-translate/commit/eec1d77))
+* **instant:** fix possible npe in case of filters with undefineds ([61a9490](https://github.com/angular-translate/angular-translate/commit/61a9490))
+* **refresh:** fix bug in refresh if using partial loader ([95c43b4](https://github.com/angular-translate/angular-translate/commit/95c43b4))
+
+### Features
+
+* **instant:** invoke missing handler within `$translate.instant(id)` ([aaf52b5](https://github.com/angular-translate/angular-translate/commit/aaf52b5))
+
+
+
+
+# [2.0.0](https://github.com/angular-translate/angular-translate/compare/1.1.1...2.0.0) (2014-02-16)
+
+
+### Bug Fixes
+
+* ***:** jshint fixes ([1e3f8a6](https://github.com/angular-translate/angular-translate/commit/1e3f8a6))
+* **$translate:** check for fallbacklanguage ([321803d](https://github.com/angular-translate/angular-translate/commit/321803d))
+* **$translate:** Trim whitespace off translationId ([4939424](https://github.com/angular-translate/angular-translate/commit/4939424))
+* **$translatePartialLoader:** fixes docs annotation ([d6ea84b](https://github.com/angular-translate/angular-translate/commit/d6ea84b))
+* **demo:** fix server routes + add index page ([eb0a2dc](https://github.com/angular-translate/angular-translate/commit/eb0a2dc))
+* **demo:** links to demo resources updated to new locactions ([fddaa49](https://github.com/angular-translate/angular-translate/commit/fddaa49))
+* **deps:** add missing resolution ([a98a2f6](https://github.com/angular-translate/angular-translate/commit/a98a2f6))
+* **docs:** fixes links for languages ([265490f](https://github.com/angular-translate/angular-translate/commit/265490f))
+* **fallbackLanguage:** Fix fallback languages loading and applying ([4c5c47c](https://github.com/angular-translate/angular-translate/commit/4c5c47c))
+* **grunt:** includes translate-cloak directive ([84a59d2](https://github.com/angular-translate/angular-translate/commit/84a59d2))
+* avoid calls with empty translationId (sub issue of #298) ([08f087b](https://github.com/angular-translate/angular-translate/commit/08f087b))
+* fix npe introduced in 4939424a30 (#281) ([173a9bc](https://github.com/angular-translate/angular-translate/commit/173a9bc)), closes [(#281](https://github.com/(/issues/281) [#298](https://github.com/angular-translate/angular-translate/issues/298)
+* **guide/ru,uk:** Fix uses->use in multi language ([af59c6a](https://github.com/angular-translate/angular-translate/commit/af59c6a))
+* **instant:** remove language-preload if there were used within instant ([9a3eda6](https://github.com/angular-translate/angular-translate/commit/9a3eda6))
+* **loader-static-files.js:** Now allows empty string as prefix and postfix. ([051f431](https://github.com/angular-translate/angular-translate/commit/051f431))
+* **service:** fallback languages could not load when using `instant()` ([26de486](https://github.com/angular-translate/angular-translate/commit/26de486))
+* **translateCloak:** makes jshint happy ([2058fd3](https://github.com/angular-translate/angular-translate/commit/2058fd3))
+* **translateDirective:** fixes bad coding convention ([d5db4ad](https://github.com/angular-translate/angular-translate/commit/d5db4ad))
+
+### Features
+
+* **$translateProvider:** adds determinePreferredLanguage() ([7cbfabe](https://github.com/angular-translate/angular-translate/commit/7cbfabe))
+* **$translateProvider:** adds registerAvailableLanguagesKeys for negotiation ([6bef6bd](https://github.com/angular-translate/angular-translate/commit/6bef6bd))
+* **filter:** filter now use $translate.instant() since promises could not use ([a1b8a17](https://github.com/angular-translate/angular-translate/commit/a1b8a17))
+* **service:** add $translate.instant() for instant translations ([3a855eb](https://github.com/angular-translate/angular-translate/commit/3a855eb))
+* add an option for post processing compiling ([d5cd943](https://github.com/angular-translate/angular-translate/commit/d5cd943))
+* add option to html escape all values ([e042c44](https://github.com/angular-translate/angular-translate/commit/e042c44))
+* **translateCloak:** adds translate-cloak directive ([c125c56](https://github.com/angular-translate/angular-translate/commit/c125c56))
+* **translateDirective:** teaches directive custom translate-value-* attr ([5c27467](https://github.com/angular-translate/angular-translate/commit/5c27467)), closes [#188](https://github.com/angular-translate/angular-translate/issues/188)
+
+
+
+
+## [1.1.1](https://github.com/angular-translate/angular-translate/compare/1.1.0...1.1.1) (2013-11-24)
+
+
+### Bug Fixes
+
+* fixes encoding ([084f08c](https://github.com/angular-translate/angular-translate/commit/084f08c))
+* **docs:** fixes typo ([7e1c4e9](https://github.com/angular-translate/angular-translate/commit/7e1c4e9))
+* **docs:** fixes typo in landing page ([0b999ab](https://github.com/angular-translate/angular-translate/commit/0b999ab))
+* **grunt:** fixes missing storage-key ([635d290](https://github.com/angular-translate/angular-translate/commit/635d290))
+* **translateDirective:** fixes occuring 'translation id undefined' erros ([bb5a2c4](https://github.com/angular-translate/angular-translate/commit/bb5a2c4))
+
+### Features
+
+* add option to html escape all values ([fe94c1f](https://github.com/angular-translate/angular-translate/commit/fe94c1f))
+* shortcuts and links\n\nShortcuts creates a shorter translationId if the last key ([f9f2cf2](https://github.com/angular-translate/angular-translate/commit/f9f2cf2))
+* Update required Node up `0.10` ([b7cf5f4](https://github.com/angular-translate/angular-translate/commit/b7cf5f4))
+
+
+
+
+# [1.1.0](https://github.com/angular-translate/angular-translate/compare/1.0.2...1.1.0) (2013-09-02)
+
+
+### Bug Fixes
+
+* **translateDirective:** fixes bug that directive writes into scope ([4e06468](https://github.com/angular-translate/angular-translate/commit/4e06468)), closes [#128](https://github.com/angular-translate/angular-translate/issues/128)
+* **translateDirective:** fixes scope handling ([c566586](https://github.com/angular-translate/angular-translate/commit/c566586))
+* **translateService:** reset proposed language if there's no pending loader ([6b477fc](https://github.com/angular-translate/angular-translate/commit/6b477fc))
+
+### Features
+
+* **$translatePartialLoader:** Basic implementation ([81222bf](https://github.com/angular-translate/angular-translate/commit/81222bf))
+* **invalidate:** added invalidate() method ([d41f91e](https://github.com/angular-translate/angular-translate/commit/d41f91e))
+* **translateProvider:** makes methods chainable ([cdc9e9e](https://github.com/angular-translate/angular-translate/commit/cdc9e9e))
+
+
+
+
+## [1.0.2](https://github.com/angular-translate/angular-translate/compare/1.0.1...1.0.2) (2013-08-07)
+
+
+### Bug Fixes
+
+* **fallbackLanguage:** fixes bug that fallbackLanguage is loaded without loader ([6aa3747](https://github.com/angular-translate/angular-translate/commit/6aa3747))
+* **translateService:** uses should only load if a loader is registered ([604daec](https://github.com/angular-translate/angular-translate/commit/604daec))
+* **typo:** remove unnecessary semicolon ([54cb232](https://github.com/angular-translate/angular-translate/commit/54cb232))
+
+
+
+
+## [1.0.1](https://github.com/angular-translate/angular-translate/compare/1.0.0...1.0.1) (2013-07-26)
+
+
+### Bug Fixes
+
+* **demo:** change src to angular-translate script ([4be93b6](https://github.com/angular-translate/angular-translate/commit/4be93b6))
+* **dependency:** add 'angular-cookies' as bower devDependency ([b6f1426](https://github.com/angular-translate/angular-translate/commit/b6f1426))
+* **platolink:** deep link ([d368bf3](https://github.com/angular-translate/angular-translate/commit/d368bf3))
+
+
+
+
+# [1.0.0](https://github.com/angular-translate/angular-translate/compare/0.9.4...1.0.0) (2013-07-23)
+
+
+### Bug Fixes
+
+* **docs:** fixes methodOf declaration of addInterpolation method ([f1eeba7](https://github.com/angular-translate/angular-translate/commit/f1eeba7))
+* **gh-pages:** plato report ([b85e19b](https://github.com/angular-translate/angular-translate/commit/b85e19b))
+* **tests:** travis CI ([c8624bf](https://github.com/angular-translate/angular-translate/commit/c8624bf))
+* **tests:** travis CI ([629bb8d](https://github.com/angular-translate/angular-translate/commit/629bb8d))
+* fixes gruntfile ([0d500db](https://github.com/angular-translate/angular-translate/commit/0d500db))
+
+### Features
+
+* **messageformat-interpolation:** implements usage of messageformat ([5596e8b](https://github.com/angular-translate/angular-translate/commit/5596e8b))
+* **translateDirective:** teaches directives to use custom interpolation ([bf3dbbb](https://github.com/angular-translate/angular-translate/commit/bf3dbbb))
+* **translateFilter:** teaches filter to use custom interpolation ([46f03cc](https://github.com/angular-translate/angular-translate/commit/46f03cc))
+* **translateService:** adds method to configure indicators for not found translations ([52a039f](https://github.com/angular-translate/angular-translate/commit/52a039f)), closes [#77](https://github.com/angular-translate/angular-translate/issues/77)
+* **translateService:** extracts default interpolation in standalone service ([5d8cb56](https://github.com/angular-translate/angular-translate/commit/5d8cb56))
+* **translateService:** implements proposedLanguage() ([6d34792](https://github.com/angular-translate/angular-translate/commit/6d34792))
+* **translateService:** implements usage of different interpolation services ([5e20e24](https://github.com/angular-translate/angular-translate/commit/5e20e24))
+* **translateService:** informs interpolator when locale has changed ([e59b141](https://github.com/angular-translate/angular-translate/commit/e59b141))
+* **translateService:** missingTranslationHandler receives language ([6fe6bb1](https://github.com/angular-translate/angular-translate/commit/6fe6bb1))
+
+
+
+
+## [0.9.4](https://github.com/angular-translate/angular-translate/compare/0.9.3...0.9.4) (2013-06-21)
+
+
+### Bug Fixes
+
+* **translateService:** fixes missingTranslationHandler-invokation bug ([525b353](https://github.com/angular-translate/angular-translate/commit/525b353)), closes [#74](https://github.com/angular-translate/angular-translate/issues/74)
+
+### Features
+
+* **translateService:** removes empty options object requirement for loaders ([c09d1db](https://github.com/angular-translate/angular-translate/commit/c09d1db))
+
+
+
+
+## [0.9.3](https://github.com/angular-translate/angular-translate/compare/0.9.2...0.9.3) (2013-06-10)
+
+
+### Features
+
+* **translateService:** let translate service handle multiple promises ([0e5d6d9](https://github.com/angular-translate/angular-translate/commit/0e5d6d9)), closes [#70](https://github.com/angular-translate/angular-translate/issues/70)
+
+
+
+
+## [0.9.2](https://github.com/angular-translate/angular-translate/compare/0.9.1...0.9.2) (2013-05-30)
+
+
+### Bug Fixes
+
+* fix bower.json ([c389882](https://github.com/angular-translate/angular-translate/commit/c389882))
+
+### Features
+
+* **translateProvider:** add fallbackLanguage() method ([018991e](https://github.com/angular-translate/angular-translate/commit/018991e)), closes [#67](https://github.com/angular-translate/angular-translate/issues/67)
+
+
+
+
+## [0.9.1](https://github.com/angular-translate/angular-translate/compare/0.9.0...0.9.1) (2013-05-25)
+
+
+### Bug Fixes
+
+* **translate.js:** Allow blank translation values ([97591a8](https://github.com/angular-translate/angular-translate/commit/97591a8))
+
+
+
+
+# [0.9.0](https://github.com/angular-translate/angular-translate/compare/0.8.1...0.9.0) (2013-05-22)
+
+
+### Features
+
+* **translateProvider:** add use*() methods for async loaders ([f2329cc](https://github.com/angular-translate/angular-translate/commit/f2329cc)), closes [#58](https://github.com/angular-translate/angular-translate/issues/58)
+
+
+
+
+## [0.8.1](https://github.com/angular-translate/angular-translate/compare/0.8.0...0.8.1) (2013-05-16)
+
+
+### Bug Fixes
+
+* **translate.js:** corrected typo ([82569f0](https://github.com/angular-translate/angular-translate/commit/82569f0))
+
+### Features
+
+* **translateProvider:** add methods to use different missingTranslationHandlers ([f6ed3e3](https://github.com/angular-translate/angular-translate/commit/f6ed3e3))
+
+
+### BREAKING CHANGES
+
+* S: missingTranslationHandler is no longer supported since its functionality will be replaced with useMissingTranslationHandlerLog.
+
+
+
+# [0.8.0](https://github.com/angular-translate/angular-translate/compare/0.7.1...0.8.0) (2013-05-14)
+
+
+
+
+
+## [0.7.1](https://github.com/angular-translate/angular-translate/compare/0.7.0...0.7.1) (2013-05-13)
+
+
+### Features
+
+* **chore:** rename ngTranslate folder to src ([65012d9](https://github.com/angular-translate/angular-translate/commit/65012d9))
+
+
+
+
+# [0.7.0](https://github.com/angular-translate/angular-translate/compare/0.6.0...0.7.0) (2013-05-12)
+
+
+### Bug Fixes
+
+* **directive:** trim off white space around element.text() ([e10173a](https://github.com/angular-translate/angular-translate/commit/e10173a))
+* **tests:** Fix preferredLanguage tests ([73efcfc](https://github.com/angular-translate/angular-translate/commit/73efcfc))
+* **tests:** fix tests for preferredLanguage() ([f1b5084](https://github.com/angular-translate/angular-translate/commit/f1b5084))
+* **tests:** Old values won't be ignored, so they have to be discarded ([625b1d6](https://github.com/angular-translate/angular-translate/commit/625b1d6))
+
+### Features
+
+* nested objects will be transformed when using `$translateProvider.translations` ([b15cee4](https://github.com/angular-translate/angular-translate/commit/b15cee4))
+* **docs:** add documentation comments ([b1efbca](https://github.com/angular-translate/angular-translate/commit/b1efbca))
+* **storageKey:** add a storageKey method ([dabf822](https://github.com/angular-translate/angular-translate/commit/dabf822))
+* **translateProvider:** add a preferredLanguage property ([563e9bf](https://github.com/angular-translate/angular-translate/commit/563e9bf))
+* **translateProvider:** add storagePrefix() method ([64cd99b](https://github.com/angular-translate/angular-translate/commit/64cd99b))
+* **translateProvider:** add useLoaderFactory() as shortcut method ([2915e8b](https://github.com/angular-translate/angular-translate/commit/2915e8b))
+* **translateProvider:** make translationTable extendable ([8e3a455](https://github.com/angular-translate/angular-translate/commit/8e3a455)), closes [#33](https://github.com/angular-translate/angular-translate/issues/33)
+* **translateProvider:** missingTranslationHandler ([3a5819e](https://github.com/angular-translate/angular-translate/commit/3a5819e))
+* **translateService:** add storage() method ([98c2b12](https://github.com/angular-translate/angular-translate/commit/98c2b12))
+
+
+### BREAKING CHANGES
+
+* The $STORAGE_KEY isn't represent a current storage key
+from now. To discover which key is used now you have to call the storageKey
+method without params.
+
+
+
+# [0.6.0](https://github.com/angular-translate/angular-translate/compare/0.5.2...0.6.0) (2013-05-03)
+
+
+### Features
+
+* **ngmin:** add grunt-ngmin ([f630958](https://github.com/angular-translate/angular-translate/commit/f630958)), closes [#20](https://github.com/angular-translate/angular-translate/issues/20)
+
+
+
+
+## [0.5.2](https://github.com/angular-translate/angular-translate/compare/0.5.1...0.5.2) (2013-04-30)
+
+
+### Bug Fixes
+
+* **translateDirective:** check for truthy value in watch callback ([98087c7](https://github.com/angular-translate/angular-translate/commit/98087c7)), closes [#18](https://github.com/angular-translate/angular-translate/issues/18)
+
+
+
+
+## [0.5.1](https://github.com/angular-translate/angular-translate/compare/0.5.0...0.5.1) (2013-04-29)
+
+
+### Features
+
+* **.bowerrc:** add .bowerrc ([42363ee](https://github.com/angular-translate/angular-translate/commit/42363ee)), closes [#16](https://github.com/angular-translate/angular-translate/issues/16)
+* **.jshintrc:** add .jshintrc ([0c8d3da](https://github.com/angular-translate/angular-translate/commit/0c8d3da)), closes [#17](https://github.com/angular-translate/angular-translate/issues/17)
+* **bower.json:** rename component.json to bower.json ([17acd10](https://github.com/angular-translate/angular-translate/commit/17acd10))
+
+
+
+
+# [0.5.0](https://github.com/angular-translate/angular-translate/compare/0.4.4...0.5.0) (2013-04-25)
+
+
+### Features
+
+* **conventional-changelogs:** Add grunt-conventional-changelog task ([c8093a7](https://github.com/angular-translate/angular-translate/commit/c8093a7)), closes [#11](https://github.com/angular-translate/angular-translate/issues/11)
+
+
+
+
+## [0.4.4](https://github.com/angular-translate/angular-translate/compare/0.4.2...0.4.4) (2013-04-23)
+
+
+
+
+
+## [0.4.2](https://github.com/angular-translate/angular-translate/compare/0.4.0...0.4.2) (2013-04-17)
+
+
+
+
+
+# [0.4.0](https://github.com/angular-translate/angular-translate/compare/0.3.0...0.4.0) (2013-04-07)
+
+
+
+
+
+# [0.3.0](https://github.com/angular-translate/angular-translate/compare/0.2.1...0.3.0) (2013-04-06)
+
+
+
+
+
+## [0.2.1](https://github.com/angular-translate/angular-translate/compare/0.2.0...0.2.1) (2013-04-05)
+
+
+
+
+
+# [0.2.0](https://github.com/angular-translate/angular-translate/compare/0.1.2...0.2.0) (2013-04-03)
+
+
+
+
+
+## [0.1.2](https://github.com/angular-translate/angular-translate/compare/0.1.1...0.1.2) (2013-04-02)
+
+
+
+
+
+## [0.1.1](https://github.com/angular-translate/angular-translate/compare/0.1.0...0.1.1) (2013-04-01)
+
+
+
+
+
+# [0.1.0](https://github.com/angular-translate/angular-translate/compare/0.0.5...0.1.0) (2013-04-01)
+
+
+
+
+
+## [0.0.5](https://github.com/angular-translate/angular-translate/compare/0.0.4...0.0.5) (2013-04-01)
+
+
+
+
+
+## [0.0.4](https://github.com/angular-translate/angular-translate/compare/0.0.2...0.0.4) (2013-04-01)
+
+
+
+
+
+## [0.0.2](https://github.com/angular-translate/angular-translate/compare/0.0.1...0.0.2) (2013-03-30)
+
+
+
+
+
+## 0.0.1 (2013-03-28)
+
+
+
+
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/LICENSE b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/LICENSE
new file mode 100644
index 0000000000..d4d931c295
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2013-2017 The angular-translate team and Pascal Precht
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/README.md b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/README.md
new file mode 100644
index 0000000000..04fa04ad70
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/README.md
@@ -0,0 +1,88 @@
+# [![angular-translate](https://raw.github.com/angular-translate/angular-translate/canary/identity/logo/angular-translate-alternative/angular-translate_alternative_medium2.png)](http://angular-translate.github.io)
+
+![Bower](https://img.shields.io/bower/v/angular-translate.svg) [![NPM](https://img.shields.io/npm/v/angular-translate.svg)](https://www.npmjs.com/package/angular-translate) [![cdnjs](https://img.shields.io/cdnjs/v/angular-translate.svg)](https://cdnjs.com/libraries/angular-translate) [![Build Status](https://img.shields.io/travis/angular-translate/angular-translate.svg)](https://travis-ci.org/angular-translate/angular-translate) ![License](https://img.shields.io/npm/l/angular-translate.svg) ![Code Climate](https://img.shields.io/codeclimate/github/angular-translate/angular-translate.svg) ![Code Coverage](https://img.shields.io/codeclimate/coverage/github/angular-translate/angular-translate.svg)
+
+This is the repository for angular-translate.
+
+angular-translate is a JavaScript translation library for AngularJS 1.x app.
+
+For more information about the angular-translate project, please visit our [website](https://angular-translate.github.io).
+
+## Status
+| Branch | Status |
+| ------------- |:-------------:|
+| master | [![Build Status](https://travis-ci.org/angular-translate/angular-translate.svg?branch=master)](https://travis-ci.org/angular-translate/angular-translate) |
+| canary |[![Build Status](https://travis-ci.org/angular-translate/angular-translate.svg?branch=canary)](https://travis-ci.org/angular-translate/angular-translate) |
+
+## Install
+We strongly *recommend* using a package manager like NPM and Bower, or even variants like Yarn or jspm.
+
+### NPM
+```
+npm install --save-dev angular-translate
+```
+
+### Bower
+```
+bower install --save-dev angular-translate
+```
+
+For more information please visit [chapter "Installation" at our website](https://angular-translate.github.io/docs/#/guide/00_installation).
+
+## Get started
+Check out out [chapter "Getting started" at out website](https://angular-translate.github.io/docs/#/guide/02_getting-started).
+
+## Get support
+Most of the time, we are getting support questions of invalid configurations. We encourage everyone to have a look at our [documentation website](https://angular-translate.github.io/docs/#/guide). If you think the documentation is not correct (bug) or should be optimized (enhancement) please file an issue.
+
+If you are still having difficulty after looking over your configuration carefully, please post a question to [StackOverflow with a specific tag](http://stackoverflow.com/questions/tagged/angular-translate). Especially if the question are related to AngularJS or even JavaScript/browser basic technologies (maybe your issue is not related to angular-translate after all).
+
+If you have discovered a bug or have a feature suggestion, feel free to create an issue on GitHub. Please follow the guideline within the issue template. See also next headline.
+
+*Please note: We cannot provide support for neither JavaScript nor AngularJS itself. In both cases, a platform like StackOverflow is much more ideal.*
+
+# Contribute
+We got a lot of great feedback from the community so far! More and more people
+use this module and they are always thankful for it and the awesome support they
+get. I just want to make sure that you guys know: All this wouldn't have been
+possible without these [great contributors](https://github.com/angular-translate/angular-translate/contributors)
+and everybody who comes with new ideas and feature requests! So **THANK YOU**!
+
+Contributing to angular-translate
is fairly easy.
+
+[This document](CONTRIBUTING.md) shows you how to
+get the project, run all provided tests and generate a production ready build.
+
+
+## Public talks
+[![Dutch AngularJS Meetup 2013](presentation.png)](https://www.youtube.com/watch?v=9CWifOK_Wi8)
+[![Kod.io 2014](presentation2.png)](https://www.youtube.com/watch?v=C7xqaExvaQ4)
+
+### Links
+* Website [angular-translate.github.io](https://angular-translate.github.io/)
+* API Reference [angular-translate.github.io/docs/#/api](https://angular-translate.github.io/docs/#/api)
+* [Contribution Guidelines](https://github.com/angular-translate/angular-translate/blob/master/CONTRIBUTING.md)
+
+### Useful resources
+There are some very useful things on the web that might be interesting for you,
+so make sure to check this list.
+
+- [Tutorial on ng-newsletter.com](http://ng-newsletter.com/posts/angular-translate.html)
+- [Examples and demos](https://github.com/angular-translate/angular-translate/wiki/Demos) - Currently on plnkr.co
+- [Tutorial on angularjs.de](http://angularjs.de/artikel/angularjs-i18n-ng-translate) - German article
+- [angular-translate on GitHub](https://github.com/angular-translate/angular-translate) - The GitHub repository
+- [angular-translate on ngmodules.org](http://ngmodules.org/modules/angular-translate)
+- [angular-translate mailinglist](https://groups.google.com/forum/#!forum/angular-translate) - Discuss, ask et al!
+- [angular-translate-quality](https://www.npmjs.com/package/angular-translate-quality) - Quality check at build time
+
+## Tests
+
+### Unit tests
+
+Note: Check that dependencies are be installed (`npm install`).
+
+The *unit tests* are available with `npm test` which is actually a shortcut for `grunt test`. It performs tests under the current primary target version of AngularJS. Use `npm run-script test-scopes` for testing other scoped versions as well.
+
+## License
+
+Licensed under MIT.
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-handler-log/angular-translate-handler-log.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-handler-log/angular-translate-handler-log.js
new file mode 100644
index 0000000000..e06d9d9862
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-handler-log/angular-translate-handler-log.js
@@ -0,0 +1,50 @@
+/*!
+ * angular-translate - v2.15.1 - 2017-03-04
+ *
+ * Copyright (c) 2017 The angular-translate team, Pascal Precht; Licensed MIT
+ */
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module unless amdModuleId is set
+ define([], function () {
+ return (factory());
+ });
+ } else if (typeof exports === 'object') {
+ // Node. Does not work with strict CommonJS, but
+ // only CommonJS-like environments that support module.exports,
+ // like Node.
+ module.exports = factory();
+ } else {
+ factory();
+ }
+}(this, function () {
+
+$translateMissingTranslationHandlerLog.$inject = ['$log'];
+angular.module('pascalprecht.translate')
+
+/**
+ * @ngdoc object
+ * @name pascalprecht.translate.$translateMissingTranslationHandlerLog
+ * @requires $log
+ *
+ * @description
+ * Uses angular's `$log` service to give a warning when trying to translate a
+ * translation id which doesn't exist.
+ *
+ * @returns {function} Handler function
+ */
+.factory('$translateMissingTranslationHandlerLog', $translateMissingTranslationHandlerLog);
+
+function $translateMissingTranslationHandlerLog ($log) {
+
+ 'use strict';
+
+ return function (translationId) {
+ $log.warn('Translation for ' + translationId + ' doesn\'t exist');
+ };
+}
+
+$translateMissingTranslationHandlerLog.displayName = '$translateMissingTranslationHandlerLog';
+return 'pascalprecht.translate';
+
+}));
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-handler-log/angular-translate-handler-log.min.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-handler-log/angular-translate-handler-log.min.js
new file mode 100644
index 0000000000..39cfdea978
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-handler-log/angular-translate-handler-log.min.js
@@ -0,0 +1,6 @@
+/*!
+ * angular-translate - v2.15.1 - 2017-03-04
+ *
+ * Copyright (c) 2017 The angular-translate team, Pascal Precht; Licensed MIT
+ */
+!function(a,b){"function"==typeof define&&define.amd?define([],function(){return b()}):"object"==typeof exports?module.exports=b():b()}(this,function(){function a(a){"use strict";return function(b){a.warn("Translation for "+b+" doesn't exist")}}return a.$inject=["$log"],angular.module("pascalprecht.translate").factory("$translateMissingTranslationHandlerLog",a),a.displayName="$translateMissingTranslationHandlerLog","pascalprecht.translate"});
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat.js
new file mode 100644
index 0000000000..e7bad11852
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat.js
@@ -0,0 +1,197 @@
+/*!
+ * angular-translate - v2.15.1 - 2017-03-04
+ *
+ * Copyright (c) 2017 The angular-translate team, Pascal Precht; Licensed MIT
+ */
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module unless amdModuleId is set
+ define(["messageformat"], function (a0) {
+ return (factory(a0));
+ });
+ } else if (typeof exports === 'object') {
+ // Node. Does not work with strict CommonJS, but
+ // only CommonJS-like environments that support module.exports,
+ // like Node.
+ module.exports = factory(require("messageformat"));
+ } else {
+ factory(root["MessageFormat"]);
+ }
+}(this, function (MessageFormat) {
+
+angular.module('pascalprecht.translate')
+
+/**
+ * @ngdoc property
+ * @name pascalprecht.translate.TRANSLATE_MF_INTERPOLATION_CACHE
+ * @requires TRANSLATE_MF_INTERPOLATION_CACHE
+ *
+ * @description
+ * Uses MessageFormat.js to interpolate strings against some values.
+ */
+.constant('TRANSLATE_MF_INTERPOLATION_CACHE', '$translateMessageFormatInterpolation')
+
+/**
+ * @ngdoc object
+ * @name pascalprecht.translate.$translateMessageFormatInterpolationProvider
+ *
+ * @description
+ * Configurations for $translateMessageFormatInterpolation
+ */
+.provider('$translateMessageFormatInterpolation', $translateMessageFormatInterpolationProvider);
+
+function $translateMessageFormatInterpolationProvider() {
+
+ 'use strict';
+
+ var configurer;
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateMessageFormatInterpolationProvider#messageFormatConfigurer
+ * @methodOf pascalprecht.translate.$translateMessageFormatInterpolationProvider
+ *
+ * @description
+ * Defines an optional configurer for the MessageFormat instance.
+ *
+ * Note: This hook will be called whenever a new instance of MessageFormat will be created.
+ *
+ * @param {function} fn callback with the instance as argument
+ */
+ this.messageFormatConfigurer = function (fn) {
+ configurer = fn;
+ };
+
+ /**
+ * @ngdoc object
+ * @name pascalprecht.translate.$translateMessageFormatInterpolation
+ * @requires pascalprecht.translate.TRANSLATE_MF_INTERPOLATION_CACHE
+ *
+ * @description
+ * Uses MessageFormat.js to interpolate strings against some values.
+ *
+ * Be aware to configure a proper sanitization strategy.
+ *
+ * See also:
+ * * {@link pascalprecht.translate.$translateSanitization}
+ * * {@link https://github.com/SlexAxton/messageformat.js}
+ *
+ * @return {object} $translateMessageFormatInterpolation Interpolator service
+ */
+ this.$get = ['$translateSanitization', '$cacheFactory', 'TRANSLATE_MF_INTERPOLATION_CACHE', function ($translateSanitization, $cacheFactory, TRANSLATE_MF_INTERPOLATION_CACHE) {
+ return $translateMessageFormatInterpolation($translateSanitization, $cacheFactory, TRANSLATE_MF_INTERPOLATION_CACHE, configurer);
+ }];
+
+}
+
+function $translateMessageFormatInterpolation($translateSanitization, $cacheFactory, TRANSLATE_MF_INTERPOLATION_CACHE, messageFormatConfigurer) {
+
+ 'use strict';
+
+ var $translateInterpolator = {},
+ $cache = $cacheFactory.get(TRANSLATE_MF_INTERPOLATION_CACHE),
+ // instantiate with default locale (which is 'en')
+ $mf = new MessageFormat('en'),
+ $identifier = 'messageformat';
+
+ if (angular.isFunction(messageFormatConfigurer)) {
+ messageFormatConfigurer($mf);
+ }
+
+ if (!$cache) {
+ // create cache if it doesn't exist already
+ $cache = $cacheFactory(TRANSLATE_MF_INTERPOLATION_CACHE);
+ }
+
+ $cache.put('en', $mf);
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateMessageFormatInterpolation#setLocale
+ * @methodOf pascalprecht.translate.$translateMessageFormatInterpolation
+ *
+ * @description
+ * Sets current locale (this is currently not use in this interpolation).
+ *
+ * @param {string} locale Language key or locale.
+ */
+ $translateInterpolator.setLocale = function (locale) {
+ $mf = $cache.get(locale);
+ if (!$mf) {
+ $mf = new MessageFormat(locale);
+ if (angular.isFunction(messageFormatConfigurer)) {
+ messageFormatConfigurer($mf);
+ }
+ $cache.put(locale, $mf);
+ }
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateMessageFormatInterpolation#getInterpolationIdentifier
+ * @methodOf pascalprecht.translate.$translateMessageFormatInterpolation
+ *
+ * @description
+ * Returns an identifier for this interpolation service.
+ *
+ * @returns {string} $identifier
+ */
+ $translateInterpolator.getInterpolationIdentifier = function () {
+ return $identifier;
+ };
+
+ /**
+ * @deprecated will be removed in 3.0
+ * @see {@link pascalprecht.translate.$translateSanitization}
+ */
+ $translateInterpolator.useSanitizeValueStrategy = function (value) {
+ $translateSanitization.useStrategy(value);
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateMessageFormatInterpolation#interpolate
+ * @methodOf pascalprecht.translate.$translateMessageFormatInterpolation
+ *
+ * @description
+ * Interpolates given string against given interpolate params using MessageFormat.js.
+ *
+ * @returns {string} interpolated string.
+ */
+ $translateInterpolator.interpolate = function (string, interpolationParams, context, sanitizeStrategy) {
+ interpolationParams = interpolationParams || {};
+ interpolationParams = $translateSanitization.sanitize(interpolationParams, 'params', sanitizeStrategy);
+
+ var compiledFunction = $cache.get('mf:' + string);
+
+ // if given string wasn't compiled yet, we do so now and never have to do it again
+ if (!compiledFunction) {
+
+ // Ensure explicit type if possible
+ // MessageFormat checks the actual type (i.e. for amount based conditions)
+ for (var key in interpolationParams) {
+ if (interpolationParams.hasOwnProperty(key)) {
+ // ensure number
+ var number = parseInt(interpolationParams[key], 10);
+ if (angular.isNumber(number) && ('' + number) === interpolationParams[key]) {
+ interpolationParams[key] = number;
+ }
+ }
+ }
+
+ compiledFunction = $mf.compile(string);
+ $cache.put('mf:' + string, compiledFunction);
+ }
+
+ var interpolatedText = compiledFunction(interpolationParams);
+ return $translateSanitization.sanitize(interpolatedText, 'text', sanitizeStrategy);
+ };
+
+ return $translateInterpolator;
+}
+
+$translateMessageFormatInterpolation.displayName = '$translateMessageFormatInterpolation';
+return 'pascalprecht.translate';
+
+}));
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat.min.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat.min.js
new file mode 100644
index 0000000000..a0147a6f25
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-interpolation-messageformat/angular-translate-interpolation-messageformat.min.js
@@ -0,0 +1,6 @@
+/*!
+ * angular-translate - v2.15.1 - 2017-03-04
+ *
+ * Copyright (c) 2017 The angular-translate team, Pascal Precht; Licensed MIT
+ */
+!function(a,b){"function"==typeof define&&define.amd?define(["messageformat"],function(a){return b(a)}):"object"==typeof exports?module.exports=b(require("messageformat")):b(a.MessageFormat)}(this,function(a){function b(){"use strict";var a;this.messageFormatConfigurer=function(b){a=b},this.$get=["$translateSanitization","$cacheFactory","TRANSLATE_MF_INTERPOLATION_CACHE",function(b,d,e){return c(b,d,e,a)}]}function c(b,c,d,e){"use strict";var f={},g=c.get(d),h=new a("en"),i="messageformat";return angular.isFunction(e)&&e(h),g||(g=c(d)),g.put("en",h),f.setLocale=function(b){h=g.get(b),h||(h=new a(b),angular.isFunction(e)&&e(h),g.put(b,h))},f.getInterpolationIdentifier=function(){return i},f.useSanitizeValueStrategy=function(a){return b.useStrategy(a),this},f.interpolate=function(a,c,d,e){c=c||{},c=b.sanitize(c,"params",e);var f=g.get("mf:"+a);if(!f){for(var i in c)if(c.hasOwnProperty(i)){var j=parseInt(c[i],10);angular.isNumber(j)&&""+j===c[i]&&(c[i]=j)}f=h.compile(a),g.put("mf:"+a,f)}var k=f(c);return b.sanitize(k,"text",e)},f}return angular.module("pascalprecht.translate").constant("TRANSLATE_MF_INTERPOLATION_CACHE","$translateMessageFormatInterpolation").provider("$translateMessageFormatInterpolation",b),c.displayName="$translateMessageFormatInterpolation","pascalprecht.translate"});
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-loader-partial/angular-translate-loader-partial.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-loader-partial/angular-translate-loader-partial.js
new file mode 100644
index 0000000000..3410f69490
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-loader-partial/angular-translate-loader-partial.js
@@ -0,0 +1,557 @@
+/*!
+ * angular-translate - v2.15.1 - 2017-03-04
+ *
+ * Copyright (c) 2017 The angular-translate team, Pascal Precht; Licensed MIT
+ */
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module unless amdModuleId is set
+ define([], function () {
+ return (factory());
+ });
+ } else if (typeof exports === 'object') {
+ // Node. Does not work with strict CommonJS, but
+ // only CommonJS-like environments that support module.exports,
+ // like Node.
+ module.exports = factory();
+ } else {
+ factory();
+ }
+}(this, function () {
+
+angular.module('pascalprecht.translate')
+/**
+ * @ngdoc object
+ * @name pascalprecht.translate.$translatePartialLoaderProvider
+ *
+ * @description
+ * By using a $translatePartialLoaderProvider you can configure a list of a needed
+ * translation parts directly during the configuration phase of your application's
+ * lifetime. All parts you add by using this provider would be loaded by
+ * angular-translate at the startup as soon as possible.
+ */
+ .provider('$translatePartialLoader', $translatePartialLoader);
+
+function $translatePartialLoader() {
+
+ 'use strict';
+
+ /**
+ * @constructor
+ * @name Part
+ *
+ * @description
+ * Represents Part object to add and set parts at runtime.
+ */
+ function Part(name, priority) {
+ this.name = name;
+ this.isActive = true;
+ this.tables = {};
+ this.priority = priority || 0;
+ this.langPromises = {};
+ }
+
+ /**
+ * @name parseUrl
+ * @method
+ *
+ * @description
+ * Returns a parsed url template string and replaces given target lang
+ * and part name it.
+ *
+ * @param {string|function} urlTemplate - Either a string containing an url pattern (with
+ * '{part}' and '{lang}') or a function(part, lang)
+ * returning a string.
+ * @param {string} targetLang - Language key for language to be used.
+ * @return {string} Parsed url template string
+ */
+ Part.prototype.parseUrl = function (urlTemplate, targetLang) {
+ if (angular.isFunction(urlTemplate)) {
+ return urlTemplate(this.name, targetLang);
+ }
+ return urlTemplate.replace(/\{part\}/g, this.name).replace(/\{lang\}/g, targetLang);
+ };
+
+ Part.prototype.getTable = function (lang, $q, $http, $httpOptions, urlTemplate, errorHandler) {
+
+ //locals
+ var self = this;
+ var lastLangPromise = this.langPromises[lang];
+ var deferred = $q.defer();
+
+ //private helper helpers
+ var fetchData = function () {
+ return $http(
+ angular.extend({
+ method : 'GET',
+ url : self.parseUrl(urlTemplate, lang)
+ },
+ $httpOptions)
+ );
+ };
+
+ //private helper
+ var handleNewData = function (data) {
+ self.tables[lang] = data;
+ deferred.resolve(data);
+ };
+
+ //private helper
+ var rejectDeferredWithPartName = function () {
+ deferred.reject(self.name);
+ };
+
+ //private helper
+ var tryGettingThisTable = function () {
+ //data fetching logic
+ fetchData().then(
+ function (result) {
+ handleNewData(result.data);
+ },
+ function (errorResponse) {
+ if (errorHandler) {
+ errorHandler(self.name, lang, errorResponse).then(handleNewData, rejectDeferredWithPartName);
+ } else {
+ rejectDeferredWithPartName();
+ }
+ });
+ };
+
+ //loading logic
+ if (!this.tables[lang]) {
+ //let's try loading the data
+ if (!lastLangPromise) {
+ //this is the first request - just go ahead and hit the server
+ tryGettingThisTable();
+ } else {
+ //this is an additional request after one or more unfinished or failed requests
+ //chain the deferred off the previous request's promise so that this request conditionally executes
+ //if the previous request succeeds then the result will be passed through, but if it fails then this request will try again and hit the server
+ lastLangPromise.then(deferred.resolve, tryGettingThisTable);
+ }
+ //retain a reference to the last promise so we can continue the chain if another request is made before any succeed
+ //you can picture the promise chain as a singly-linked list (formed by the .then handler queues) that's traversed by the execution context
+ this.langPromises[lang] = deferred.promise;
+ }
+ else {
+ //the part has already been loaded - if lastLangPromise is also undefined then the table has been populated using setPart
+ //this breaks the promise chain because we're not tying langDeferred's outcome to a previous call's promise handler queues, but we don't care because there's no asynchronous execution context to keep track of anymore
+ deferred.resolve(this.tables[lang]);
+ }
+ return deferred.promise;
+ };
+
+ var parts = {};
+
+ function hasPart(name) {
+ return Object.prototype.hasOwnProperty.call(parts, name);
+ }
+
+ function isStringValid(str) {
+ return angular.isString(str) && str !== '';
+ }
+
+ function isPartAvailable(name) {
+ if (!isStringValid(name)) {
+ throw new TypeError('Invalid type of a first argument, a non-empty string expected.');
+ }
+
+ return (hasPart(name) && parts[name].isActive);
+ }
+
+ function deepExtend(dst, src) {
+ for (var property in src) {
+ if (src[property] && src[property].constructor &&
+ src[property].constructor === Object) {
+ dst[property] = dst[property] || {};
+ deepExtend(dst[property], src[property]);
+ } else {
+ dst[property] = src[property];
+ }
+ }
+ return dst;
+ }
+
+ function getPrioritizedParts() {
+ var prioritizedParts = [];
+ for (var part in parts) {
+ if (parts[part].isActive) {
+ prioritizedParts.push(parts[part]);
+ }
+ }
+ prioritizedParts.sort(function (a, b) {
+ return a.priority - b.priority;
+ });
+ return prioritizedParts;
+ }
+
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translatePartialLoaderProvider#addPart
+ * @methodOf pascalprecht.translate.$translatePartialLoaderProvider
+ *
+ * @description
+ * Registers a new part of the translation table to be loaded once the
+ * `angular-translate` gets into runtime phase. It does not actually load any
+ * translation data, but only registers a part to be loaded in the future.
+ *
+ * @param {string} name A name of the part to add
+ * @param {int} [priority=0] Sets the load priority of this part.
+ *
+ * @returns {object} $translatePartialLoaderProvider, so this method is chainable
+ * @throws {TypeError} The method could throw a **TypeError** if you pass the param
+ * of the wrong type. Please, note that the `name` param has to be a
+ * non-empty **string**.
+ */
+ this.addPart = function (name, priority) {
+ if (!isStringValid(name)) {
+ throw new TypeError('Couldn\'t add part, part name has to be a string!');
+ }
+
+ if (!hasPart(name)) {
+ parts[name] = new Part(name, priority);
+ }
+ parts[name].isActive = true;
+
+ return this;
+ };
+
+ /**
+ * @ngdocs function
+ * @name pascalprecht.translate.$translatePartialLoaderProvider#setPart
+ * @methodOf pascalprecht.translate.$translatePartialLoaderProvider
+ *
+ * @description
+ * Sets a translation table to the specified part. This method does not make the
+ * specified part available, but only avoids loading this part from the server.
+ *
+ * @param {string} lang A language of the given translation table
+ * @param {string} part A name of the target part
+ * @param {object} table A translation table to set to the specified part
+ *
+ * @return {object} $translatePartialLoaderProvider, so this method is chainable
+ * @throws {TypeError} The method could throw a **TypeError** if you pass params
+ * of the wrong type. Please, note that the `lang` and `part` params have to be a
+ * non-empty **string**s and the `table` param has to be an object.
+ */
+ this.setPart = function (lang, part, table) {
+ if (!isStringValid(lang)) {
+ throw new TypeError('Couldn\'t set part.`lang` parameter has to be a string!');
+ }
+ if (!isStringValid(part)) {
+ throw new TypeError('Couldn\'t set part.`part` parameter has to be a string!');
+ }
+ if (typeof table !== 'object' || table === null) {
+ throw new TypeError('Couldn\'t set part. `table` parameter has to be an object!');
+ }
+
+ if (!hasPart(part)) {
+ parts[part] = new Part(part);
+ parts[part].isActive = false;
+ }
+
+ parts[part].tables[lang] = table;
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translatePartialLoaderProvider#deletePart
+ * @methodOf pascalprecht.translate.$translatePartialLoaderProvider
+ *
+ * @description
+ * Removes the previously added part of the translation data. So, `angular-translate` will not
+ * load it at the startup.
+ *
+ * @param {string} name A name of the part to delete
+ *
+ * @returns {object} $translatePartialLoaderProvider, so this method is chainable
+ *
+ * @throws {TypeError} The method could throw a **TypeError** if you pass the param of the wrong
+ * type. Please, note that the `name` param has to be a non-empty **string**.
+ */
+ this.deletePart = function (name) {
+ if (!isStringValid(name)) {
+ throw new TypeError('Couldn\'t delete part, first arg has to be string.');
+ }
+
+ if (hasPart(name)) {
+ parts[name].isActive = false;
+ }
+
+ return this;
+ };
+
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translatePartialLoaderProvider#isPartAvailable
+ * @methodOf pascalprecht.translate.$translatePartialLoaderProvider
+ *
+ * @description
+ * Checks if the specific part is available. A part becomes available after it was added by the
+ * `addPart` method. Available parts would be loaded from the server once the `angular-translate`
+ * asks the loader to that.
+ *
+ * @param {string} name A name of the part to check
+ *
+ * @returns {boolean} Returns **true** if the part is available now and **false** if not.
+ *
+ * @throws {TypeError} The method could throw a **TypeError** if you pass the param of the wrong
+ * type. Please, note that the `name` param has to be a non-empty **string**.
+ */
+ this.isPartAvailable = isPartAvailable;
+
+ /**
+ * @ngdoc object
+ * @name pascalprecht.translate.$translatePartialLoader
+ *
+ * @requires $q
+ * @requires $http
+ * @requires $injector
+ * @requires $rootScope
+ * @requires $translate
+ *
+ * @description
+ *
+ * @param {object} options Options object
+ *
+ * @throws {TypeError}
+ */
+ this.$get = ['$rootScope', '$injector', '$q', '$http',
+ function ($rootScope, $injector, $q, $http) {
+
+ /**
+ * @ngdoc event
+ * @name pascalprecht.translate.$translatePartialLoader#$translatePartialLoaderStructureChanged
+ * @eventOf pascalprecht.translate.$translatePartialLoader
+ * @eventType broadcast on root scope
+ *
+ * @description
+ * A $translatePartialLoaderStructureChanged event is called when a state of the loader was
+ * changed somehow. It could mean either some part is added or some part is deleted. Anyway when
+ * you get this event the translation table is not longer current and has to be updated.
+ *
+ * @param {string} name A name of the part which is a reason why the event was fired
+ */
+
+ var service = function (options) {
+ if (!isStringValid(options.key)) {
+ throw new TypeError('Unable to load data, a key is not a non-empty string.');
+ }
+
+ if (!isStringValid(options.urlTemplate) && !angular.isFunction(options.urlTemplate)) {
+ throw new TypeError('Unable to load data, a urlTemplate is not a non-empty string or not a function.');
+ }
+
+ var errorHandler = options.loadFailureHandler;
+ if (errorHandler !== undefined) {
+ if (!angular.isString(errorHandler)) {
+ throw new Error('Unable to load data, a loadFailureHandler is not a string.');
+ } else {
+ errorHandler = $injector.get(errorHandler);
+ }
+ }
+
+ var loaders = [],
+ prioritizedParts = getPrioritizedParts();
+
+ angular.forEach(prioritizedParts, function (part) {
+ loaders.push(
+ part.getTable(options.key, $q, $http, options.$http, options.urlTemplate, errorHandler)
+ );
+ part.urlTemplate = options.urlTemplate;
+ });
+
+ return $q.all(loaders)
+ .then(function () {
+ var table = {};
+ prioritizedParts = getPrioritizedParts();
+ angular.forEach(prioritizedParts, function (part) {
+ deepExtend(table, part.tables[options.key]);
+ });
+ return table;
+ }, function () {
+ return $q.reject(options.key);
+ });
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translatePartialLoader#addPart
+ * @methodOf pascalprecht.translate.$translatePartialLoader
+ *
+ * @description
+ * Registers a new part of the translation table. This method does not actually perform any xhr
+ * requests to get translation data. The new parts will be loaded in order of priority from the server next time
+ * `angular-translate` asks the loader to load translations.
+ *
+ * @param {string} name A name of the part to add
+ * @param {int} [priority=0] Sets the load priority of this part.
+ *
+ * @returns {object} $translatePartialLoader, so this method is chainable
+ *
+ * @fires {$translatePartialLoaderStructureChanged} The $translatePartialLoaderStructureChanged
+ * event would be fired by this method in case the new part affected somehow on the loaders
+ * state. This way it means that there are a new translation data available to be loaded from
+ * the server.
+ *
+ * @throws {TypeError} The method could throw a **TypeError** if you pass the param of the wrong
+ * type. Please, note that the `name` param has to be a non-empty **string**.
+ */
+ service.addPart = function (name, priority) {
+ if (!isStringValid(name)) {
+ throw new TypeError('Couldn\'t add part, first arg has to be a string');
+ }
+
+ if (!hasPart(name)) {
+ parts[name] = new Part(name, priority);
+ $rootScope.$emit('$translatePartialLoaderStructureChanged', name);
+ } else if (!parts[name].isActive) {
+ parts[name].isActive = true;
+ $rootScope.$emit('$translatePartialLoaderStructureChanged', name);
+ }
+
+ return service;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translatePartialLoader#deletePart
+ * @methodOf pascalprecht.translate.$translatePartialLoader
+ *
+ * @description
+ * Deletes the previously added part of the translation data. The target part could be deleted
+ * either logically or physically. When the data is deleted logically it is not actually deleted
+ * from the browser, but the loader marks it as not active and prevents it from affecting on the
+ * translations. If the deleted in such way part is added again, the loader will use the
+ * previously loaded data rather than loading it from the server once more time. But if the data
+ * is deleted physically, the loader will completely remove all information about it. So in case
+ * of recycling this part will be loaded from the server again.
+ *
+ * @param {string} name A name of the part to delete
+ * @param {boolean=} [removeData=false] An indicator if the loader has to remove a loaded
+ * translation data physically. If the `removeData` if set to **false** the loaded data will not be
+ * deleted physically and might be reused in the future to prevent an additional xhr requests.
+ *
+ * @returns {object} $translatePartialLoader, so this method is chainable
+ *
+ * @fires {$translatePartialLoaderStructureChanged} The $translatePartialLoaderStructureChanged
+ * event would be fired by this method in case a part deletion process affects somehow on the
+ * loaders state. This way it means that some part of the translation data is now deprecated and
+ * the translation table has to be recompiled with the remaining translation parts.
+ *
+ * @throws {TypeError} The method could throw a **TypeError** if you pass some param of the
+ * wrong type. Please, note that the `name` param has to be a non-empty **string** and
+ * the `removeData` param has to be either **undefined** or **boolean**.
+ */
+ service.deletePart = function (name, removeData) {
+ if (!isStringValid(name)) {
+ throw new TypeError('Couldn\'t delete part, first arg has to be string');
+ }
+
+ if (removeData === undefined) {
+ removeData = false;
+ } else if (typeof removeData !== 'boolean') {
+ throw new TypeError('Invalid type of a second argument, a boolean expected.');
+ }
+
+ if (hasPart(name)) {
+ var wasActive = parts[name].isActive;
+ if (removeData) {
+ var $translate = $injector.get('$translate');
+ var cache = $translate.loaderCache();
+ if (typeof(cache) === 'string') {
+ // getting on-demand instance of loader
+ cache = $injector.get(cache);
+ }
+ // Purging items from cache...
+ if (typeof(cache) === 'object') {
+ angular.forEach(parts[name].tables, function (value, key) {
+ cache.remove(parts[name].parseUrl(parts[name].urlTemplate, key));
+ });
+ }
+ delete parts[name];
+ } else {
+ parts[name].isActive = false;
+ }
+ if (wasActive) {
+ $rootScope.$emit('$translatePartialLoaderStructureChanged', name);
+ }
+ }
+
+ return service;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translatePartialLoader#isPartLoaded
+ * @methodOf pascalprecht.translate.$translatePartialLoader
+ *
+ * @description
+ * Checks if the registered translation part is loaded into the translation table.
+ *
+ * @param {string} name A name of the part
+ * @param {string} lang A key of the language
+ *
+ * @returns {boolean} Returns **true** if the translation of the part is loaded to the translation table and **false** if not.
+ *
+ * @throws {TypeError} The method could throw a **TypeError** if you pass the param of the wrong
+ * type. Please, note that the `name` and `lang` params have to be non-empty **string**.
+ */
+ service.isPartLoaded = function (name, lang) {
+ return angular.isDefined(parts[name]) && angular.isDefined(parts[name].tables[lang]);
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translatePartialLoader#getRegisteredParts
+ * @methodOf pascalprecht.translate.$translatePartialLoader
+ *
+ * @description
+ * Gets names of the parts that were added with the `addPart`.
+ *
+ * @returns {array} Returns array of registered parts, if none were registered then an empty array is returned.
+ */
+ service.getRegisteredParts = function () {
+ var registeredParts = [];
+ angular.forEach(parts, function (p) {
+ if (p.isActive) {
+ registeredParts.push(p.name);
+ }
+ });
+ return registeredParts;
+ };
+
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translatePartialLoader#isPartAvailable
+ * @methodOf pascalprecht.translate.$translatePartialLoader
+ *
+ * @description
+ * Checks if a target translation part is available. The part becomes available just after it was
+ * added by the `addPart` method. Part's availability does not mean that it was loaded from the
+ * server, but only that it was added to the loader. The available part might be loaded next
+ * time the loader is called.
+ *
+ * @param {string} name A name of the part to delete
+ *
+ * @returns {boolean} Returns **true** if the part is available now and **false** if not.
+ *
+ * @throws {TypeError} The method could throw a **TypeError** if you pass the param of the wrong
+ * type. Please, note that the `name` param has to be a non-empty **string**.
+ */
+ service.isPartAvailable = isPartAvailable;
+
+ return service;
+
+ }];
+
+}
+
+$translatePartialLoader.displayName = '$translatePartialLoader';
+return 'pascalprecht.translate';
+
+}));
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-loader-partial/angular-translate-loader-partial.min.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-loader-partial/angular-translate-loader-partial.min.js
new file mode 100644
index 0000000000..a7bfd99aed
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-loader-partial/angular-translate-loader-partial.min.js
@@ -0,0 +1,6 @@
+/*!
+ * angular-translate - v2.15.1 - 2017-03-04
+ *
+ * Copyright (c) 2017 The angular-translate team, Pascal Precht; Licensed MIT
+ */
+!function(a,b){"function"==typeof define&&define.amd?define([],function(){return b()}):"object"==typeof exports?module.exports=b():b()}(this,function(){function a(){"use strict";function a(a,b){this.name=a,this.isActive=!0,this.tables={},this.priority=b||0,this.langPromises={}}function b(a){return Object.prototype.hasOwnProperty.call(g,a)}function c(a){return angular.isString(a)&&""!==a}function d(a){if(!c(a))throw new TypeError("Invalid type of a first argument, a non-empty string expected.");return b(a)&&g[a].isActive}function e(a,b){for(var c in b)b[c]&&b[c].constructor&&b[c].constructor===Object?(a[c]=a[c]||{},e(a[c],b[c])):a[c]=b[c];return a}function f(){var a=[];for(var b in g)g[b].isActive&&a.push(g[b]);return a.sort(function(a,b){return a.priority-b.priority}),a}a.prototype.parseUrl=function(a,b){return angular.isFunction(a)?a(this.name,b):a.replace(/\{part\}/g,this.name).replace(/\{lang\}/g,b)},a.prototype.getTable=function(a,b,c,d,e,f){var g=this,h=this.langPromises[a],i=b.defer(),j=function(){return c(angular.extend({method:"GET",url:g.parseUrl(e,a)},d))},k=function(b){g.tables[a]=b,i.resolve(b)},l=function(){i.reject(g.name)},m=function(){j().then(function(a){k(a.data)},function(b){f?f(g.name,a,b).then(k,l):l()})};return this.tables[a]?i.resolve(this.tables[a]):(h?h.then(i.resolve,m):m(),this.langPromises[a]=i.promise),i.promise};var g={};this.addPart=function(d,e){if(!c(d))throw new TypeError("Couldn't add part, part name has to be a string!");return b(d)||(g[d]=new a(d,e)),g[d].isActive=!0,this},this.setPart=function(d,e,f){if(!c(d))throw new TypeError("Couldn't set part.`lang` parameter has to be a string!");if(!c(e))throw new TypeError("Couldn't set part.`part` parameter has to be a string!");if("object"!=typeof f||null===f)throw new TypeError("Couldn't set part. `table` parameter has to be an object!");return b(e)||(g[e]=new a(e),g[e].isActive=!1),g[e].tables[d]=f,this},this.deletePart=function(a){if(!c(a))throw new TypeError("Couldn't delete part, first arg has to be string.");return b(a)&&(g[a].isActive=!1),this},this.isPartAvailable=d,this.$get=["$rootScope","$injector","$q","$http",function(h,i,j,k){var l=function(a){if(!c(a.key))throw new TypeError("Unable to load data, a key is not a non-empty string.");if(!c(a.urlTemplate)&&!angular.isFunction(a.urlTemplate))throw new TypeError("Unable to load data, a urlTemplate is not a non-empty string or not a function.");var b=a.loadFailureHandler;if(void 0!==b){if(!angular.isString(b))throw new Error("Unable to load data, a loadFailureHandler is not a string.");b=i.get(b)}var d=[],g=f();return angular.forEach(g,function(c){d.push(c.getTable(a.key,j,k,a.$http,a.urlTemplate,b)),c.urlTemplate=a.urlTemplate}),j.all(d).then(function(){var b={};return g=f(),angular.forEach(g,function(c){e(b,c.tables[a.key])}),b},function(){return j.reject(a.key)})};return l.addPart=function(d,e){if(!c(d))throw new TypeError("Couldn't add part, first arg has to be a string");return b(d)?g[d].isActive||(g[d].isActive=!0,h.$emit("$translatePartialLoaderStructureChanged",d)):(g[d]=new a(d,e),h.$emit("$translatePartialLoaderStructureChanged",d)),l},l.deletePart=function(a,d){if(!c(a))throw new TypeError("Couldn't delete part, first arg has to be string");if(void 0===d)d=!1;else if("boolean"!=typeof d)throw new TypeError("Invalid type of a second argument, a boolean expected.");if(b(a)){var e=g[a].isActive;if(d){var f=i.get("$translate"),j=f.loaderCache();"string"==typeof j&&(j=i.get(j)),"object"==typeof j&&angular.forEach(g[a].tables,function(b,c){j.remove(g[a].parseUrl(g[a].urlTemplate,c))}),delete g[a]}else g[a].isActive=!1;e&&h.$emit("$translatePartialLoaderStructureChanged",a)}return l},l.isPartLoaded=function(a,b){return angular.isDefined(g[a])&&angular.isDefined(g[a].tables[b])},l.getRegisteredParts=function(){var a=[];return angular.forEach(g,function(b){b.isActive&&a.push(b.name)}),a},l.isPartAvailable=d,l}]}return angular.module("pascalprecht.translate").provider("$translatePartialLoader",a),a.displayName="$translatePartialLoader","pascalprecht.translate"});
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-loader-static-files/angular-translate-loader-static-files.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-loader-static-files/angular-translate-loader-static-files.js
new file mode 100644
index 0000000000..a68fbc7fbd
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-loader-static-files/angular-translate-loader-static-files.js
@@ -0,0 +1,112 @@
+/*!
+ * angular-translate - v2.15.1 - 2017-03-04
+ *
+ * Copyright (c) 2017 The angular-translate team, Pascal Precht; Licensed MIT
+ */
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module unless amdModuleId is set
+ define([], function () {
+ return (factory());
+ });
+ } else if (typeof exports === 'object') {
+ // Node. Does not work with strict CommonJS, but
+ // only CommonJS-like environments that support module.exports,
+ // like Node.
+ module.exports = factory();
+ } else {
+ factory();
+ }
+}(this, function () {
+
+$translateStaticFilesLoader.$inject = ['$q', '$http'];
+angular.module('pascalprecht.translate')
+/**
+ * @ngdoc object
+ * @name pascalprecht.translate.$translateStaticFilesLoader
+ * @requires $q
+ * @requires $http
+ *
+ * @description
+ * Creates a loading function for a typical static file url pattern:
+ * "lang-en_US.json", "lang-de_DE.json", etc. Using this builder,
+ * the response of these urls must be an object of key-value pairs.
+ *
+ * @param {object} options Options object, which gets prefix, suffix, key, and fileMap
+ */
+.factory('$translateStaticFilesLoader', $translateStaticFilesLoader);
+
+function $translateStaticFilesLoader($q, $http) {
+
+ 'use strict';
+
+ return function (options) {
+
+ if (!options || (!angular.isArray(options.files) && (!angular.isString(options.prefix) || !angular.isString(options.suffix)))) {
+ throw new Error('Couldn\'t load static files, no files and prefix or suffix specified!');
+ }
+
+ if (!options.files) {
+ options.files = [{
+ prefix: options.prefix,
+ suffix: options.suffix
+ }];
+ }
+
+ var load = function (file) {
+ if (!file || (!angular.isString(file.prefix) || !angular.isString(file.suffix))) {
+ throw new Error('Couldn\'t load static file, no prefix or suffix specified!');
+ }
+
+ var fileUrl = [
+ file.prefix,
+ options.key,
+ file.suffix
+ ].join('');
+
+ if (angular.isObject(options.fileMap) && options.fileMap[fileUrl]) {
+ fileUrl = options.fileMap[fileUrl];
+ }
+
+ return $http(angular.extend({
+ url: fileUrl,
+ method: 'GET'
+ }, options.$http))
+ .then(function(result) {
+ return result.data;
+ }, function () {
+ return $q.reject(options.key);
+ });
+ };
+
+ var promises = [],
+ length = options.files.length;
+
+ for (var i = 0; i < length; i++) {
+ promises.push(load({
+ prefix: options.files[i].prefix,
+ key: options.key,
+ suffix: options.files[i].suffix
+ }));
+ }
+
+ return $q.all(promises)
+ .then(function (data) {
+ var length = data.length,
+ mergedData = {};
+
+ for (var i = 0; i < length; i++) {
+ for (var key in data[i]) {
+ mergedData[key] = data[i][key];
+ }
+ }
+
+ return mergedData;
+ });
+ };
+}
+
+$translateStaticFilesLoader.displayName = '$translateStaticFilesLoader';
+return 'pascalprecht.translate';
+
+}));
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-loader-static-files/angular-translate-loader-static-files.min.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-loader-static-files/angular-translate-loader-static-files.min.js
new file mode 100644
index 0000000000..8ba0b602af
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-loader-static-files/angular-translate-loader-static-files.min.js
@@ -0,0 +1,6 @@
+/*!
+ * angular-translate - v2.15.1 - 2017-03-04
+ *
+ * Copyright (c) 2017 The angular-translate team, Pascal Precht; Licensed MIT
+ */
+!function(a,b){"function"==typeof define&&define.amd?define([],function(){return b()}):"object"==typeof exports?module.exports=b():b()}(this,function(){function a(a,b){"use strict";return function(c){if(!(c&&(angular.isArray(c.files)||angular.isString(c.prefix)&&angular.isString(c.suffix))))throw new Error("Couldn't load static files, no files and prefix or suffix specified!");c.files||(c.files=[{prefix:c.prefix,suffix:c.suffix}]);for(var d=function(d){if(!d||!angular.isString(d.prefix)||!angular.isString(d.suffix))throw new Error("Couldn't load static file, no prefix or suffix specified!");var e=[d.prefix,c.key,d.suffix].join("");return angular.isObject(c.fileMap)&&c.fileMap[e]&&(e=c.fileMap[e]),b(angular.extend({url:e,method:"GET"},c.$http)).then(function(a){return a.data},function(){return a.reject(c.key)})},e=[],f=c.files.length,g=0;g= 4) {
+ var $cookies = $injector.get('$cookies');
+ delegate = {
+ get : function (key) {
+ return $cookies.get(key);
+ },
+ put : function (key, value) {
+ $cookies.put(key, value);
+ }
+ };
+ } else {
+ var $cookieStore = $injector.get('$cookieStore');
+ delegate = {
+ get : function (key) {
+ return $cookieStore.get(key);
+ },
+ put : function (key, value) {
+ $cookieStore.put(key, value);
+ }
+ };
+ }
+
+ var $translateCookieStorage = {
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateCookieStorage#get
+ * @methodOf pascalprecht.translate.$translateCookieStorage
+ *
+ * @description
+ * Returns an item from cookieStorage by given name.
+ *
+ * @param {string} name Item name
+ * @return {string} Value of item name
+ */
+ get : function (name) {
+ return delegate.get(name);
+ },
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateCookieStorage#set
+ * @methodOf pascalprecht.translate.$translateCookieStorage
+ *
+ * @description
+ * Sets an item in cookieStorage by given name.
+ *
+ * @deprecated use #put
+ *
+ * @param {string} name Item name
+ * @param {string} value Item value
+ */
+ set : function (name, value) {
+ delegate.put(name, value);
+ },
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateCookieStorage#put
+ * @methodOf pascalprecht.translate.$translateCookieStorage
+ *
+ * @description
+ * Sets an item in cookieStorage by given name.
+ *
+ * @param {string} name Item name
+ * @param {string} value Item value
+ */
+ put : function (name, value) {
+ delegate.put(name, value);
+ }
+ };
+
+ return $translateCookieStorage;
+}
+
+$translateCookieStorageFactory.displayName = '$translateCookieStorage';
+return 'pascalprecht.translate';
+
+}));
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-storage-cookie/angular-translate-storage-cookie.min.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-storage-cookie/angular-translate-storage-cookie.min.js
new file mode 100644
index 0000000000..54467c58ac
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-storage-cookie/angular-translate-storage-cookie.min.js
@@ -0,0 +1,6 @@
+/*!
+ * angular-translate - v2.15.1 - 2017-03-04
+ *
+ * Copyright (c) 2017 The angular-translate team, Pascal Precht; Licensed MIT
+ */
+!function(a,b){"function"==typeof define&&define.amd?define([],function(){return b()}):"object"==typeof exports?module.exports=b():b()}(this,function(){function a(a){"use strict";var b;if(1===angular.version.major&&angular.version.minor>=4){var c=a.get("$cookies");b={get:function(a){return c.get(a)},put:function(a,b){c.put(a,b)}}}else{var d=a.get("$cookieStore");b={get:function(a){return d.get(a)},put:function(a,b){d.put(a,b)}}}var e={get:function(a){return b.get(a)},set:function(a,c){b.put(a,c)},put:function(a,c){b.put(a,c)}};return e}return a.$inject=["$injector"],angular.module("pascalprecht.translate").factory("$translateCookieStorage",a),a.displayName="$translateCookieStorage","pascalprecht.translate"});
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-storage-local/angular-translate-storage-local.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-storage-local/angular-translate-storage-local.js
new file mode 100644
index 0000000000..e8998ed9c9
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-storage-local/angular-translate-storage-local.js
@@ -0,0 +1,123 @@
+/*!
+ * angular-translate - v2.15.1 - 2017-03-04
+ *
+ * Copyright (c) 2017 The angular-translate team, Pascal Precht; Licensed MIT
+ */
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module unless amdModuleId is set
+ define([], function () {
+ return (factory());
+ });
+ } else if (typeof exports === 'object') {
+ // Node. Does not work with strict CommonJS, but
+ // only CommonJS-like environments that support module.exports,
+ // like Node.
+ module.exports = factory();
+ } else {
+ factory();
+ }
+}(this, function () {
+
+$translateLocalStorageFactory.$inject = ['$window', '$translateCookieStorage'];
+angular.module('pascalprecht.translate')
+
+/**
+ * @ngdoc object
+ * @name pascalprecht.translate.$translateLocalStorage
+ * @requires $window
+ * @requires $translateCookieStorage
+ *
+ * @description
+ * Abstraction layer for localStorage. This service is used when telling angular-translate
+ * to use localStorage as storage.
+ *
+ */
+.factory('$translateLocalStorage', $translateLocalStorageFactory);
+
+function $translateLocalStorageFactory($window, $translateCookieStorage) {
+
+ 'use strict';
+
+ // Setup adapter
+ var localStorageAdapter = (function(){
+ var langKey;
+ return {
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateLocalStorage#get
+ * @methodOf pascalprecht.translate.$translateLocalStorage
+ *
+ * @description
+ * Returns an item from localStorage by given name.
+ *
+ * @param {string} name Item name
+ * @return {string} Value of item name
+ */
+ get: function (name) {
+ if(!langKey) {
+ langKey = $window.localStorage.getItem(name);
+ }
+
+ return langKey;
+ },
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateLocalStorage#set
+ * @methodOf pascalprecht.translate.$translateLocalStorage
+ *
+ * @description
+ * Sets an item in localStorage by given name.
+ *
+ * @deprecated use #put
+ *
+ * @param {string} name Item name
+ * @param {string} value Item value
+ */
+ set: function (name, value) {
+ langKey=value;
+ $window.localStorage.setItem(name, value);
+ },
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateLocalStorage#put
+ * @methodOf pascalprecht.translate.$translateLocalStorage
+ *
+ * @description
+ * Sets an item in localStorage by given name.
+ *
+ * @param {string} name Item name
+ * @param {string} value Item value
+ */
+ put: function (name, value) {
+ langKey=value;
+ $window.localStorage.setItem(name, value);
+ }
+ };
+ }());
+
+ var hasLocalStorageSupport = 'localStorage' in $window;
+ if (hasLocalStorageSupport) {
+ var testKey = 'pascalprecht.translate.storageTest';
+ try {
+ // this check have to be wrapped within a try/catch because on
+ // a SecurityError: Dom Exception 18 on iOS
+ if ($window.localStorage !== null) {
+ $window.localStorage.setItem(testKey, 'foo');
+ $window.localStorage.removeItem(testKey);
+ hasLocalStorageSupport = true;
+ } else {
+ hasLocalStorageSupport = false;
+ }
+ } catch (e){
+ hasLocalStorageSupport = false;
+ }
+ }
+ var $translateLocalStorage = hasLocalStorageSupport ? localStorageAdapter : $translateCookieStorage;
+ return $translateLocalStorage;
+}
+
+$translateLocalStorageFactory.displayName = '$translateLocalStorageFactory';
+return 'pascalprecht.translate';
+
+}));
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-storage-local/angular-translate-storage-local.min.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-storage-local/angular-translate-storage-local.min.js
new file mode 100644
index 0000000000..785ca42d14
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate-storage-local/angular-translate-storage-local.min.js
@@ -0,0 +1,6 @@
+/*!
+ * angular-translate - v2.15.1 - 2017-03-04
+ *
+ * Copyright (c) 2017 The angular-translate team, Pascal Precht; Licensed MIT
+ */
+!function(a,b){"function"==typeof define&&define.amd?define([],function(){return b()}):"object"==typeof exports?module.exports=b():b()}(this,function(){function a(a,b){"use strict";var c=function(){var b;return{get:function(c){return b||(b=a.localStorage.getItem(c)),b},set:function(c,d){b=d,a.localStorage.setItem(c,d)},put:function(c,d){b=d,a.localStorage.setItem(c,d)}}}(),d="localStorage"in a;if(d){var e="pascalprecht.translate.storageTest";try{null!==a.localStorage?(a.localStorage.setItem(e,"foo"),a.localStorage.removeItem(e),d=!0):d=!1}catch(a){d=!1}}var f=d?c:b;return f}return a.$inject=["$window","$translateCookieStorage"],angular.module("pascalprecht.translate").factory("$translateLocalStorage",a),a.displayName="$translateLocalStorageFactory","pascalprecht.translate"});
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate.js
new file mode 100644
index 0000000000..c2e2e142dc
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate.js
@@ -0,0 +1,3709 @@
+/*!
+ * angular-translate - v2.15.1 - 2017-03-04
+ *
+ * Copyright (c) 2017 The angular-translate team, Pascal Precht; Licensed MIT
+ */
+(function (root, factory) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register as an anonymous module unless amdModuleId is set
+ define([], function () {
+ return (factory());
+ });
+ } else if (typeof exports === 'object') {
+ // Node. Does not work with strict CommonJS, but
+ // only CommonJS-like environments that support module.exports,
+ // like Node.
+ module.exports = factory();
+ } else {
+ factory();
+ }
+}(this, function () {
+
+/**
+ * @ngdoc overview
+ * @name pascalprecht.translate
+ *
+ * @description
+ * The main module which holds everything together.
+ */
+runTranslate.$inject = ['$translate'];
+$translate.$inject = ['$STORAGE_KEY', '$windowProvider', '$translateSanitizationProvider', 'pascalprechtTranslateOverrider'];
+$translateDefaultInterpolation.$inject = ['$interpolate', '$translateSanitization'];
+translateDirective.$inject = ['$translate', '$interpolate', '$compile', '$parse', '$rootScope'];
+translateAttrDirective.$inject = ['$translate', '$rootScope'];
+translateCloakDirective.$inject = ['$translate', '$rootScope'];
+translateFilterFactory.$inject = ['$parse', '$translate'];
+$translationCache.$inject = ['$cacheFactory'];
+angular.module('pascalprecht.translate', ['ng'])
+ .run(runTranslate);
+
+function runTranslate($translate) {
+
+ 'use strict';
+
+ var key = $translate.storageKey(),
+ storage = $translate.storage();
+
+ var fallbackFromIncorrectStorageValue = function () {
+ var preferred = $translate.preferredLanguage();
+ if (angular.isString(preferred)) {
+ $translate.use(preferred);
+ // $translate.use() will also remember the language.
+ // So, we don't need to call storage.put() here.
+ } else {
+ storage.put(key, $translate.use());
+ }
+ };
+
+ fallbackFromIncorrectStorageValue.displayName = 'fallbackFromIncorrectStorageValue';
+
+ if (storage) {
+ if (!storage.get(key)) {
+ fallbackFromIncorrectStorageValue();
+ } else {
+ $translate.use(storage.get(key))['catch'](fallbackFromIncorrectStorageValue);
+ }
+ } else if (angular.isString($translate.preferredLanguage())) {
+ $translate.use($translate.preferredLanguage());
+ }
+}
+
+runTranslate.displayName = 'runTranslate';
+
+/**
+ * @ngdoc object
+ * @name pascalprecht.translate.$translateSanitizationProvider
+ *
+ * @description
+ *
+ * Configurations for $translateSanitization
+ */
+angular.module('pascalprecht.translate').provider('$translateSanitization', $translateSanitizationProvider);
+
+function $translateSanitizationProvider () {
+
+ 'use strict';
+
+ var $sanitize,
+ $sce,
+ currentStrategy = null, // TODO change to either 'sanitize', 'escape' or ['sanitize', 'escapeParameters'] in 3.0.
+ hasConfiguredStrategy = false,
+ hasShownNoStrategyConfiguredWarning = false,
+ strategies;
+
+ /**
+ * Definition of a sanitization strategy function
+ * @callback StrategyFunction
+ * @param {string|object} value - value to be sanitized (either a string or an interpolated value map)
+ * @param {string} mode - either 'text' for a string (translation) or 'params' for the interpolated params
+ * @return {string|object}
+ */
+
+ /**
+ * @ngdoc property
+ * @name strategies
+ * @propertyOf pascalprecht.translate.$translateSanitizationProvider
+ *
+ * @description
+ * Following strategies are built-in:
+ *
+ * sanitize
+ * Sanitizes HTML in the translation text using $sanitize
+ * escape
+ * Escapes HTML in the translation
+ * sanitizeParameters
+ * Sanitizes HTML in the values of the interpolation parameters using $sanitize
+ * escapeParameters
+ * Escapes HTML in the values of the interpolation parameters
+ * escaped
+ * Support legacy strategy name 'escaped' for backwards compatibility (will be removed in 3.0)
+ *
+ *
+ */
+
+ strategies = {
+ sanitize: function (value, mode/*, context*/) {
+ if (mode === 'text') {
+ value = htmlSanitizeValue(value);
+ }
+ return value;
+ },
+ escape: function (value, mode/*, context*/) {
+ if (mode === 'text') {
+ value = htmlEscapeValue(value);
+ }
+ return value;
+ },
+ sanitizeParameters: function (value, mode/*, context*/) {
+ if (mode === 'params') {
+ value = mapInterpolationParameters(value, htmlSanitizeValue);
+ }
+ return value;
+ },
+ escapeParameters: function (value, mode/*, context*/) {
+ if (mode === 'params') {
+ value = mapInterpolationParameters(value, htmlEscapeValue);
+ }
+ return value;
+ },
+ sce: function (value, mode, context) {
+ if (mode === 'text') {
+ value = htmlTrustValue(value);
+ } else if (mode === 'params') {
+ if (context !== 'filter') {
+ // do html escape in filter context #1101
+ value = mapInterpolationParameters(value, htmlEscapeValue);
+ }
+ }
+ return value;
+ },
+ sceParameters: function (value, mode/*, context*/) {
+ if (mode === 'params') {
+ value = mapInterpolationParameters(value, htmlTrustValue);
+ }
+ return value;
+ }
+ };
+ // Support legacy strategy name 'escaped' for backwards compatibility.
+ // TODO should be removed in 3.0
+ strategies.escaped = strategies.escapeParameters;
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateSanitizationProvider#addStrategy
+ * @methodOf pascalprecht.translate.$translateSanitizationProvider
+ *
+ * @description
+ * Adds a sanitization strategy to the list of known strategies.
+ *
+ * @param {string} strategyName - unique key for a strategy
+ * @param {StrategyFunction} strategyFunction - strategy function
+ * @returns {object} this
+ */
+ this.addStrategy = function (strategyName, strategyFunction) {
+ strategies[strategyName] = strategyFunction;
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateSanitizationProvider#removeStrategy
+ * @methodOf pascalprecht.translate.$translateSanitizationProvider
+ *
+ * @description
+ * Removes a sanitization strategy from the list of known strategies.
+ *
+ * @param {string} strategyName - unique key for a strategy
+ * @returns {object} this
+ */
+ this.removeStrategy = function (strategyName) {
+ delete strategies[strategyName];
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateSanitizationProvider#useStrategy
+ * @methodOf pascalprecht.translate.$translateSanitizationProvider
+ *
+ * @description
+ * Selects a sanitization strategy. When an array is provided the strategies will be executed in order.
+ *
+ * @param {string|StrategyFunction|array} strategy The sanitization strategy / strategies which should be used. Either a name of an existing strategy, a custom strategy function, or an array consisting of multiple names and / or custom functions.
+ * @returns {object} this
+ */
+ this.useStrategy = function (strategy) {
+ hasConfiguredStrategy = true;
+ currentStrategy = strategy;
+ return this;
+ };
+
+ /**
+ * @ngdoc object
+ * @name pascalprecht.translate.$translateSanitization
+ * @requires $injector
+ * @requires $log
+ *
+ * @description
+ * Sanitizes interpolation parameters and translated texts.
+ *
+ */
+ this.$get = ['$injector', '$log', function ($injector, $log) {
+
+ var cachedStrategyMap = {};
+
+ var applyStrategies = function (value, mode, context, selectedStrategies) {
+ angular.forEach(selectedStrategies, function (selectedStrategy) {
+ if (angular.isFunction(selectedStrategy)) {
+ value = selectedStrategy(value, mode, context);
+ } else if (angular.isFunction(strategies[selectedStrategy])) {
+ value = strategies[selectedStrategy](value, mode, context);
+ } else if (angular.isString(strategies[selectedStrategy])) {
+ if (!cachedStrategyMap[strategies[selectedStrategy]]) {
+ try {
+ cachedStrategyMap[strategies[selectedStrategy]] = $injector.get(strategies[selectedStrategy]);
+ } catch (e) {
+ cachedStrategyMap[strategies[selectedStrategy]] = function() {};
+ throw new Error('pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: \'' + selectedStrategy + '\'');
+ }
+ }
+ value = cachedStrategyMap[strategies[selectedStrategy]](value, mode, context);
+ } else {
+ throw new Error('pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: \'' + selectedStrategy + '\'');
+ }
+ });
+ return value;
+ };
+
+ // TODO: should be removed in 3.0
+ var showNoStrategyConfiguredWarning = function () {
+ if (!hasConfiguredStrategy && !hasShownNoStrategyConfiguredWarning) {
+ $log.warn('pascalprecht.translate.$translateSanitization: No sanitization strategy has been configured. This can have serious security implications. See http://angular-translate.github.io/docs/#/guide/19_security for details.');
+ hasShownNoStrategyConfiguredWarning = true;
+ }
+ };
+
+ if ($injector.has('$sanitize')) {
+ $sanitize = $injector.get('$sanitize');
+ }
+ if ($injector.has('$sce')) {
+ $sce = $injector.get('$sce');
+ }
+
+ return {
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateSanitization#useStrategy
+ * @methodOf pascalprecht.translate.$translateSanitization
+ *
+ * @description
+ * Selects a sanitization strategy. When an array is provided the strategies will be executed in order.
+ *
+ * @param {string|StrategyFunction|array} strategy The sanitization strategy / strategies which should be used. Either a name of an existing strategy, a custom strategy function, or an array consisting of multiple names and / or custom functions.
+ */
+ useStrategy: (function (self) {
+ return function (strategy) {
+ self.useStrategy(strategy);
+ };
+ })(this),
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateSanitization#sanitize
+ * @methodOf pascalprecht.translate.$translateSanitization
+ *
+ * @description
+ * Sanitizes a value.
+ *
+ * @param {string|object} value The value which should be sanitized.
+ * @param {string} mode The current sanitization mode, either 'params' or 'text'.
+ * @param {string|StrategyFunction|array} [strategy] Optional custom strategy which should be used instead of the currently selected strategy.
+ * @param {string} [context] The context of this call: filter, service. Default is service
+ * @returns {string|object} sanitized value
+ */
+ sanitize: function (value, mode, strategy, context) {
+ if (!currentStrategy) {
+ showNoStrategyConfiguredWarning();
+ }
+
+ if (!strategy && strategy !== null) {
+ strategy = currentStrategy;
+ }
+
+ if (!strategy) {
+ return value;
+ }
+
+ if (!context) {
+ context = 'service';
+ }
+
+ var selectedStrategies = angular.isArray(strategy) ? strategy : [strategy];
+ return applyStrategies(value, mode, context, selectedStrategies);
+ }
+ };
+ }];
+
+ var htmlEscapeValue = function (value) {
+ var element = angular.element('
');
+ element.text(value); // not chainable, see #1044
+ return element.html();
+ };
+
+ var htmlSanitizeValue = function (value) {
+ if (!$sanitize) {
+ throw new Error('pascalprecht.translate.$translateSanitization: Error cannot find $sanitize service. Either include the ngSanitize module (https://docs.angularjs.org/api/ngSanitize) or use a sanitization strategy which does not depend on $sanitize, such as \'escape\'.');
+ }
+ return $sanitize(value);
+ };
+
+ var htmlTrustValue = function (value) {
+ if (!$sce) {
+ throw new Error('pascalprecht.translate.$translateSanitization: Error cannot find $sce service.');
+ }
+ return $sce.trustAsHtml(value);
+ };
+
+ var mapInterpolationParameters = function (value, iteratee, stack) {
+ if (angular.isDate(value)) {
+ return value;
+ } else if (angular.isObject(value)) {
+ var result = angular.isArray(value) ? [] : {};
+
+ if (!stack) {
+ stack = [];
+ } else {
+ if (stack.indexOf(value) > -1) {
+ throw new Error('pascalprecht.translate.$translateSanitization: Error cannot interpolate parameter due recursive object');
+ }
+ }
+
+ stack.push(value);
+ angular.forEach(value, function (propertyValue, propertyKey) {
+
+ /* Skipping function properties. */
+ if (angular.isFunction(propertyValue)) {
+ return;
+ }
+
+ result[propertyKey] = mapInterpolationParameters(propertyValue, iteratee, stack);
+ });
+ stack.splice(-1, 1); // remove last
+
+ return result;
+ } else if (angular.isNumber(value)) {
+ return value;
+ } else if (!angular.isUndefined(value) && value !== null) {
+ return iteratee(value);
+ } else {
+ return value;
+ }
+ };
+}
+
+/**
+ * @ngdoc object
+ * @name pascalprecht.translate.$translateProvider
+ * @description
+ *
+ * $translateProvider allows developers to register translation-tables, asynchronous loaders
+ * and similar to configure translation behavior directly inside of a module.
+ *
+ */
+angular.module('pascalprecht.translate')
+ .constant('pascalprechtTranslateOverrider', {})
+ .provider('$translate', $translate);
+
+function $translate($STORAGE_KEY, $windowProvider, $translateSanitizationProvider, pascalprechtTranslateOverrider) {
+
+ 'use strict';
+
+ var $translationTable = {},
+ $preferredLanguage,
+ $availableLanguageKeys = [],
+ $languageKeyAliases,
+ $fallbackLanguage,
+ $fallbackWasString,
+ $uses,
+ $nextLang,
+ $storageFactory,
+ $storageKey = $STORAGE_KEY,
+ $storagePrefix,
+ $missingTranslationHandlerFactory,
+ $interpolationFactory,
+ $interpolatorFactories = [],
+ $loaderFactory,
+ $cloakClassName = 'translate-cloak',
+ $loaderOptions,
+ $notFoundIndicatorLeft,
+ $notFoundIndicatorRight,
+ $postCompilingEnabled = false,
+ $forceAsyncReloadEnabled = false,
+ $nestedObjectDelimeter = '.',
+ $isReady = false,
+ $keepContent = false,
+ loaderCache,
+ directivePriority = 0,
+ statefulFilter = true,
+ postProcessFn,
+ uniformLanguageTagResolver = 'default',
+ languageTagResolver = {
+ 'default' : function (tag) {
+ return (tag || '').split('-').join('_');
+ },
+ java : function (tag) {
+ var temp = (tag || '').split('-').join('_');
+ var parts = temp.split('_');
+ return parts.length > 1 ? (parts[0].toLowerCase() + '_' + parts[1].toUpperCase()) : temp;
+ },
+ bcp47 : function (tag) {
+ var temp = (tag || '').split('_').join('-');
+ var parts = temp.split('-');
+ return parts.length > 1 ? (parts[0].toLowerCase() + '-' + parts[1].toUpperCase()) : temp;
+ },
+ 'iso639-1' : function (tag) {
+ var temp = (tag || '').split('_').join('-');
+ var parts = temp.split('-');
+ return parts[0].toLowerCase();
+ }
+ };
+
+ var version = '2.15.1';
+
+ // tries to determine the browsers language
+ var getFirstBrowserLanguage = function () {
+
+ // internal purpose only
+ if (angular.isFunction(pascalprechtTranslateOverrider.getLocale)) {
+ return pascalprechtTranslateOverrider.getLocale();
+ }
+
+ var nav = $windowProvider.$get().navigator,
+ browserLanguagePropertyKeys = ['language', 'browserLanguage', 'systemLanguage', 'userLanguage'],
+ i,
+ language;
+
+ // support for HTML 5.1 "navigator.languages"
+ if (angular.isArray(nav.languages)) {
+ for (i = 0; i < nav.languages.length; i++) {
+ language = nav.languages[i];
+ if (language && language.length) {
+ return language;
+ }
+ }
+ }
+
+ // support for other well known properties in browsers
+ for (i = 0; i < browserLanguagePropertyKeys.length; i++) {
+ language = nav[browserLanguagePropertyKeys[i]];
+ if (language && language.length) {
+ return language;
+ }
+ }
+
+ return null;
+ };
+ getFirstBrowserLanguage.displayName = 'angular-translate/service: getFirstBrowserLanguage';
+
+ // tries to determine the browsers locale
+ var getLocale = function () {
+ var locale = getFirstBrowserLanguage() || '';
+ if (languageTagResolver[uniformLanguageTagResolver]) {
+ locale = languageTagResolver[uniformLanguageTagResolver](locale);
+ }
+ return locale;
+ };
+ getLocale.displayName = 'angular-translate/service: getLocale';
+
+ /**
+ * @name indexOf
+ * @private
+ *
+ * @description
+ * indexOf polyfill. Kinda sorta.
+ *
+ * @param {array} array Array to search in.
+ * @param {string} searchElement Element to search for.
+ *
+ * @returns {int} Index of search element.
+ */
+ var indexOf = function (array, searchElement) {
+ for (var i = 0, len = array.length; i < len; i++) {
+ if (array[i] === searchElement) {
+ return i;
+ }
+ }
+ return -1;
+ };
+
+ /**
+ * @name trim
+ * @private
+ *
+ * @description
+ * trim polyfill
+ *
+ * @returns {string} The string stripped of whitespace from both ends
+ */
+ var trim = function () {
+ return this.toString().replace(/^\s+|\s+$/g, '');
+ };
+
+ var negotiateLocale = function (preferred) {
+ if (!preferred) {
+ return;
+ }
+
+ var avail = [],
+ locale = angular.lowercase(preferred),
+ i = 0,
+ n = $availableLanguageKeys.length;
+
+ for (; i < n; i++) {
+ avail.push(angular.lowercase($availableLanguageKeys[i]));
+ }
+
+ // Check for an exact match in our list of available keys
+ if (indexOf(avail, locale) > -1) {
+ return preferred;
+ }
+
+ if ($languageKeyAliases) {
+ var alias;
+ for (var langKeyAlias in $languageKeyAliases) {
+ if ($languageKeyAliases.hasOwnProperty(langKeyAlias)) {
+ var hasWildcardKey = false;
+ var hasExactKey = Object.prototype.hasOwnProperty.call($languageKeyAliases, langKeyAlias) &&
+ angular.lowercase(langKeyAlias) === angular.lowercase(preferred);
+
+ if (langKeyAlias.slice(-1) === '*') {
+ hasWildcardKey = langKeyAlias.slice(0, -1) === preferred.slice(0, langKeyAlias.length - 1);
+ }
+ if (hasExactKey || hasWildcardKey) {
+ alias = $languageKeyAliases[langKeyAlias];
+ if (indexOf(avail, angular.lowercase(alias)) > -1) {
+ return alias;
+ }
+ }
+ }
+ }
+ }
+
+ // Check for a language code without region
+ var parts = preferred.split('_');
+
+ if (parts.length > 1 && indexOf(avail, angular.lowercase(parts[0])) > -1) {
+ return parts[0];
+ }
+
+ // If everything fails, return undefined.
+ return;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#translations
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Registers a new translation table for specific language key.
+ *
+ * To register a translation table for specific language, pass a defined language
+ * key as first parameter.
+ *
+ *
+ * // register translation table for language: 'de_DE'
+ * $translateProvider.translations('de_DE', {
+ * 'GREETING': 'Hallo Welt!'
+ * });
+ *
+ * // register another one
+ * $translateProvider.translations('en_US', {
+ * 'GREETING': 'Hello world!'
+ * });
+ *
+ *
+ * When registering multiple translation tables for for the same language key,
+ * the actual translation table gets extended. This allows you to define module
+ * specific translation which only get added, once a specific module is loaded in
+ * your app.
+ *
+ * Invoking this method with no arguments returns the translation table which was
+ * registered with no language key. Invoking it with a language key returns the
+ * related translation table.
+ *
+ * @param {string} langKey A language key.
+ * @param {object} translationTable A plain old JavaScript object that represents a translation table.
+ *
+ */
+ var translations = function (langKey, translationTable) {
+
+ if (!langKey && !translationTable) {
+ return $translationTable;
+ }
+
+ if (langKey && !translationTable) {
+ if (angular.isString(langKey)) {
+ return $translationTable[langKey];
+ }
+ } else {
+ if (!angular.isObject($translationTable[langKey])) {
+ $translationTable[langKey] = {};
+ }
+ angular.extend($translationTable[langKey], flatObject(translationTable));
+ }
+ return this;
+ };
+
+ this.translations = translations;
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#cloakClassName
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ *
+ * Let's you change the class name for `translate-cloak` directive.
+ * Default class name is `translate-cloak`.
+ *
+ * @param {string} name translate-cloak class name
+ */
+ this.cloakClassName = function (name) {
+ if (!name) {
+ return $cloakClassName;
+ }
+ $cloakClassName = name;
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#nestedObjectDelimeter
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ *
+ * Let's you change the delimiter for namespaced translations.
+ * Default delimiter is `.`.
+ *
+ * @param {string} delimiter namespace separator
+ */
+ this.nestedObjectDelimeter = function (delimiter) {
+ if (!delimiter) {
+ return $nestedObjectDelimeter;
+ }
+ $nestedObjectDelimeter = delimiter;
+ return this;
+ };
+
+ /**
+ * @name flatObject
+ * @private
+ *
+ * @description
+ * Flats an object. This function is used to flatten given translation data with
+ * namespaces, so they are later accessible via dot notation.
+ */
+ var flatObject = function (data, path, result, prevKey) {
+ var key, keyWithPath, keyWithShortPath, val;
+
+ if (!path) {
+ path = [];
+ }
+ if (!result) {
+ result = {};
+ }
+ for (key in data) {
+ if (!Object.prototype.hasOwnProperty.call(data, key)) {
+ continue;
+ }
+ val = data[key];
+ if (angular.isObject(val)) {
+ flatObject(val, path.concat(key), result, key);
+ } else {
+ keyWithPath = path.length ? ('' + path.join($nestedObjectDelimeter) + $nestedObjectDelimeter + key) : key;
+ if (path.length && key === prevKey) {
+ // Create shortcut path (foo.bar == foo.bar.bar)
+ keyWithShortPath = '' + path.join($nestedObjectDelimeter);
+ // Link it to original path
+ result[keyWithShortPath] = '@:' + keyWithPath;
+ }
+ result[keyWithPath] = val;
+ }
+ }
+ return result;
+ };
+ flatObject.displayName = 'flatObject';
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#addInterpolation
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Adds interpolation services to angular-translate, so it can manage them.
+ *
+ * @param {object} factory Interpolation service factory
+ */
+ this.addInterpolation = function (factory) {
+ $interpolatorFactories.push(factory);
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useMessageFormatInterpolation
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate to use interpolation functionality of messageformat.js.
+ * This is useful when having high level pluralization and gender selection.
+ */
+ this.useMessageFormatInterpolation = function () {
+ return this.useInterpolation('$translateMessageFormatInterpolation');
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useInterpolation
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate which interpolation style to use as default, application-wide.
+ * Simply pass a factory/service name. The interpolation service has to implement
+ * the correct interface.
+ *
+ * @param {string} factory Interpolation service name.
+ */
+ this.useInterpolation = function (factory) {
+ $interpolationFactory = factory;
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useSanitizeStrategy
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Simply sets a sanitation strategy type.
+ *
+ * @param {string} value Strategy type.
+ */
+ this.useSanitizeValueStrategy = function (value) {
+ $translateSanitizationProvider.useStrategy(value);
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#preferredLanguage
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells the module which of the registered translation tables to use for translation
+ * at initial startup by passing a language key. Similar to `$translateProvider#use`
+ * only that it says which language to **prefer**.
+ *
+ * @param {string} langKey A language key.
+ */
+ this.preferredLanguage = function (langKey) {
+ if (langKey) {
+ setupPreferredLanguage(langKey);
+ return this;
+ }
+ return $preferredLanguage;
+ };
+ var setupPreferredLanguage = function (langKey) {
+ if (langKey) {
+ $preferredLanguage = langKey;
+ }
+ return $preferredLanguage;
+ };
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicator
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Sets an indicator which is used when a translation isn't found. E.g. when
+ * setting the indicator as 'X' and one tries to translate a translation id
+ * called `NOT_FOUND`, this will result in `X NOT_FOUND X`.
+ *
+ * Internally this methods sets a left indicator and a right indicator using
+ * `$translateProvider.translationNotFoundIndicatorLeft()` and
+ * `$translateProvider.translationNotFoundIndicatorRight()`.
+ *
+ * **Note**: These methods automatically add a whitespace between the indicators
+ * and the translation id.
+ *
+ * @param {string} indicator An indicator, could be any string.
+ */
+ this.translationNotFoundIndicator = function (indicator) {
+ this.translationNotFoundIndicatorLeft(indicator);
+ this.translationNotFoundIndicatorRight(indicator);
+ return this;
+ };
+
+ /**
+ * ngdoc function
+ * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicatorLeft
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Sets an indicator which is used when a translation isn't found left to the
+ * translation id.
+ *
+ * @param {string} indicator An indicator.
+ */
+ this.translationNotFoundIndicatorLeft = function (indicator) {
+ if (!indicator) {
+ return $notFoundIndicatorLeft;
+ }
+ $notFoundIndicatorLeft = indicator;
+ return this;
+ };
+
+ /**
+ * ngdoc function
+ * @name pascalprecht.translate.$translateProvider#translationNotFoundIndicatorLeft
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Sets an indicator which is used when a translation isn't found right to the
+ * translation id.
+ *
+ * @param {string} indicator An indicator.
+ */
+ this.translationNotFoundIndicatorRight = function (indicator) {
+ if (!indicator) {
+ return $notFoundIndicatorRight;
+ }
+ $notFoundIndicatorRight = indicator;
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#fallbackLanguage
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells the module which of the registered translation tables to use when missing translations
+ * at initial startup by passing a language key. Similar to `$translateProvider#use`
+ * only that it says which language to **fallback**.
+ *
+ * @param {string||array} langKey A language key.
+ *
+ */
+ this.fallbackLanguage = function (langKey) {
+ fallbackStack(langKey);
+ return this;
+ };
+
+ var fallbackStack = function (langKey) {
+ if (langKey) {
+ if (angular.isString(langKey)) {
+ $fallbackWasString = true;
+ $fallbackLanguage = [langKey];
+ } else if (angular.isArray(langKey)) {
+ $fallbackWasString = false;
+ $fallbackLanguage = langKey;
+ }
+ if (angular.isString($preferredLanguage) && indexOf($fallbackLanguage, $preferredLanguage) < 0) {
+ $fallbackLanguage.push($preferredLanguage);
+ }
+
+ return this;
+ } else {
+ if ($fallbackWasString) {
+ return $fallbackLanguage[0];
+ } else {
+ return $fallbackLanguage;
+ }
+ }
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#use
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Set which translation table to use for translation by given language key. When
+ * trying to 'use' a language which isn't provided, it'll throw an error.
+ *
+ * You actually don't have to use this method since `$translateProvider#preferredLanguage`
+ * does the job too.
+ *
+ * @param {string} langKey A language key.
+ */
+ this.use = function (langKey) {
+ if (langKey) {
+ if (!$translationTable[langKey] && (!$loaderFactory)) {
+ // only throw an error, when not loading translation data asynchronously
+ throw new Error('$translateProvider couldn\'t find translationTable for langKey: \'' + langKey + '\'');
+ }
+ $uses = langKey;
+ return this;
+ }
+ return $uses;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#resolveClientLocale
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * This returns the current browser/client's language key. The result is processed with the configured uniform tag resolver.
+ *
+ * @returns {string} the current client/browser language key
+ */
+ this.resolveClientLocale = function () {
+ return getLocale();
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#storageKey
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells the module which key must represent the choosed language by a user in the storage.
+ *
+ * @param {string} key A key for the storage.
+ */
+ var storageKey = function (key) {
+ if (!key) {
+ if ($storagePrefix) {
+ return $storagePrefix + $storageKey;
+ }
+ return $storageKey;
+ }
+ $storageKey = key;
+ return this;
+ };
+
+ this.storageKey = storageKey;
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useUrlLoader
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate to use `$translateUrlLoader` extension service as loader.
+ *
+ * @param {string} url Url
+ * @param {Object=} options Optional configuration object
+ */
+ this.useUrlLoader = function (url, options) {
+ return this.useLoader('$translateUrlLoader', angular.extend({url : url}, options));
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useStaticFilesLoader
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate to use `$translateStaticFilesLoader` extension service as loader.
+ *
+ * @param {Object=} options Optional configuration object
+ */
+ this.useStaticFilesLoader = function (options) {
+ return this.useLoader('$translateStaticFilesLoader', options);
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useLoader
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate to use any other service as loader.
+ *
+ * @param {string} loaderFactory Factory name to use
+ * @param {Object=} options Optional configuration object
+ */
+ this.useLoader = function (loaderFactory, options) {
+ $loaderFactory = loaderFactory;
+ $loaderOptions = options || {};
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useLocalStorage
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate to use `$translateLocalStorage` service as storage layer.
+ *
+ */
+ this.useLocalStorage = function () {
+ return this.useStorage('$translateLocalStorage');
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useCookieStorage
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate to use `$translateCookieStorage` service as storage layer.
+ */
+ this.useCookieStorage = function () {
+ return this.useStorage('$translateCookieStorage');
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useStorage
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate to use custom service as storage layer.
+ */
+ this.useStorage = function (storageFactory) {
+ $storageFactory = storageFactory;
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#storagePrefix
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Sets prefix for storage key.
+ *
+ * @param {string} prefix Storage key prefix
+ */
+ this.storagePrefix = function (prefix) {
+ if (!prefix) {
+ return prefix;
+ }
+ $storagePrefix = prefix;
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useMissingTranslationHandlerLog
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate to use built-in log handler when trying to translate
+ * a translation Id which doesn't exist.
+ *
+ * This is actually a shortcut method for `useMissingTranslationHandler()`.
+ *
+ */
+ this.useMissingTranslationHandlerLog = function () {
+ return this.useMissingTranslationHandler('$translateMissingTranslationHandlerLog');
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useMissingTranslationHandler
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Expects a factory name which later gets instantiated with `$injector`.
+ * This method can be used to tell angular-translate to use a custom
+ * missingTranslationHandler. Just build a factory which returns a function
+ * and expects a translation id as argument.
+ *
+ * Example:
+ *
+ * app.config(function ($translateProvider) {
+ * $translateProvider.useMissingTranslationHandler('customHandler');
+ * });
+ *
+ * app.factory('customHandler', function (dep1, dep2) {
+ * return function (translationId) {
+ * // something with translationId and dep1 and dep2
+ * };
+ * });
+ *
+ *
+ * @param {string} factory Factory name
+ */
+ this.useMissingTranslationHandler = function (factory) {
+ $missingTranslationHandlerFactory = factory;
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#usePostCompiling
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * If post compiling is enabled, all translated values will be processed
+ * again with AngularJS' $compile.
+ *
+ * Example:
+ *
+ * app.config(function ($translateProvider) {
+ * $translateProvider.usePostCompiling(true);
+ * });
+ *
+ *
+ * @param {string} factory Factory name
+ */
+ this.usePostCompiling = function (value) {
+ $postCompilingEnabled = !(!value);
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#forceAsyncReload
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * If force async reload is enabled, async loader will always be called
+ * even if $translationTable already contains the language key, adding
+ * possible new entries to the $translationTable.
+ *
+ * Example:
+ *
+ * app.config(function ($translateProvider) {
+ * $translateProvider.forceAsyncReload(true);
+ * });
+ *
+ *
+ * @param {boolean} value - valid values are true or false
+ */
+ this.forceAsyncReload = function (value) {
+ $forceAsyncReloadEnabled = !(!value);
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#uniformLanguageTag
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate which language tag should be used as a result when determining
+ * the current browser language.
+ *
+ * This setting must be set before invoking {@link pascalprecht.translate.$translateProvider#methods_determinePreferredLanguage determinePreferredLanguage()}.
+ *
+ *
+ * $translateProvider
+ * .uniformLanguageTag('bcp47')
+ * .determinePreferredLanguage()
+ *
+ *
+ * The resolver currently supports:
+ * * default
+ * (traditionally: hyphens will be converted into underscores, i.e. en-US => en_US)
+ * en-US => en_US
+ * en_US => en_US
+ * en-us => en_us
+ * * java
+ * like default, but the second part will be always in uppercase
+ * en-US => en_US
+ * en_US => en_US
+ * en-us => en_US
+ * * BCP 47 (RFC 4646 & 4647)
+ * en-US => en-US
+ * en_US => en-US
+ * en-us => en-US
+ *
+ * See also:
+ * * http://en.wikipedia.org/wiki/IETF_language_tag
+ * * http://www.w3.org/International/core/langtags/
+ * * http://tools.ietf.org/html/bcp47
+ *
+ * @param {string|object} options - options (or standard)
+ * @param {string} options.standard - valid values are 'default', 'bcp47', 'java'
+ */
+ this.uniformLanguageTag = function (options) {
+
+ if (!options) {
+ options = {};
+ } else if (angular.isString(options)) {
+ options = {
+ standard : options
+ };
+ }
+
+ uniformLanguageTagResolver = options.standard;
+
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#determinePreferredLanguage
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Tells angular-translate to try to determine on its own which language key
+ * to set as preferred language. When `fn` is given, angular-translate uses it
+ * to determine a language key, otherwise it uses the built-in `getLocale()`
+ * method.
+ *
+ * The `getLocale()` returns a language key in the format `[lang]_[country]` or
+ * `[lang]` depending on what the browser provides.
+ *
+ * Use this method at your own risk, since not all browsers return a valid
+ * locale (see {@link pascalprecht.translate.$translateProvider#methods_uniformLanguageTag uniformLanguageTag()}).
+ *
+ * @param {Function=} fn Function to determine a browser's locale
+ */
+ this.determinePreferredLanguage = function (fn) {
+
+ var locale = (fn && angular.isFunction(fn)) ? fn() : getLocale();
+
+ if (!$availableLanguageKeys.length) {
+ $preferredLanguage = locale;
+ } else {
+ $preferredLanguage = negotiateLocale(locale) || locale;
+ }
+
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#registerAvailableLanguageKeys
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Registers a set of language keys the app will work with. Use this method in
+ * combination with
+ * {@link pascalprecht.translate.$translateProvider#determinePreferredLanguage determinePreferredLanguage}.
+ * When available languages keys are registered, angular-translate
+ * tries to find the best fitting language key depending on the browsers locale,
+ * considering your language key convention.
+ *
+ * @param {object} languageKeys Array of language keys the your app will use
+ * @param {object=} aliases Alias map.
+ */
+ this.registerAvailableLanguageKeys = function (languageKeys, aliases) {
+ if (languageKeys) {
+ $availableLanguageKeys = languageKeys;
+ if (aliases) {
+ $languageKeyAliases = aliases;
+ }
+ return this;
+ }
+ return $availableLanguageKeys;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#useLoaderCache
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Registers a cache for internal $http based loaders.
+ * {@link pascalprecht.translate.$translationCache $translationCache}.
+ * When false the cache will be disabled (default). When true or undefined
+ * the cache will be a default (see $cacheFactory). When an object it will
+ * be treat as a cache object itself: the usage is $http({cache: cache})
+ *
+ * @param {object} cache boolean, string or cache-object
+ */
+ this.useLoaderCache = function (cache) {
+ if (cache === false) {
+ // disable cache
+ loaderCache = undefined;
+ } else if (cache === true) {
+ // enable cache using AJS defaults
+ loaderCache = true;
+ } else if (typeof(cache) === 'undefined') {
+ // enable cache using default
+ loaderCache = '$translationCache';
+ } else if (cache) {
+ // enable cache using given one (see $cacheFactory)
+ loaderCache = cache;
+ }
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#directivePriority
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Sets the default priority of the translate directive. The standard value is `0`.
+ * Calling this function without an argument will return the current value.
+ *
+ * @param {number} priority for the translate-directive
+ */
+ this.directivePriority = function (priority) {
+ if (priority === undefined) {
+ // getter
+ return directivePriority;
+ } else {
+ // setter with chaining
+ directivePriority = priority;
+ return this;
+ }
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#statefulFilter
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * Since AngularJS 1.3, filters which are not stateless (depending at the scope)
+ * have to explicit define this behavior.
+ * Sets whether the translate filter should be stateful or stateless. The standard value is `true`
+ * meaning being stateful.
+ * Calling this function without an argument will return the current value.
+ *
+ * @param {boolean} state - defines the state of the filter
+ */
+ this.statefulFilter = function (state) {
+ if (state === undefined) {
+ // getter
+ return statefulFilter;
+ } else {
+ // setter with chaining
+ statefulFilter = state;
+ return this;
+ }
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#postProcess
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * The post processor will be intercept right after the translation result. It can modify the result.
+ *
+ * @param {object} fn Function or service name (string) to be called after the translation value has been set / resolved. The function itself will enrich every value being processed and then continue the normal resolver process
+ */
+ this.postProcess = function (fn) {
+ if (fn) {
+ postProcessFn = fn;
+ } else {
+ postProcessFn = undefined;
+ }
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateProvider#keepContent
+ * @methodOf pascalprecht.translate.$translateProvider
+ *
+ * @description
+ * If keepContent is set to true than translate directive will always use innerHTML
+ * as a default translation
+ *
+ * Example:
+ *
+ * app.config(function ($translateProvider) {
+ * $translateProvider.keepContent(true);
+ * });
+ *
+ *
+ * @param {boolean} value - valid values are true or false
+ */
+ this.keepContent = function (value) {
+ $keepContent = !(!value);
+ return this;
+ };
+
+ /**
+ * @ngdoc object
+ * @name pascalprecht.translate.$translate
+ * @requires $interpolate
+ * @requires $log
+ * @requires $rootScope
+ * @requires $q
+ *
+ * @description
+ * The `$translate` service is the actual core of angular-translate. It expects a translation id
+ * and optional interpolate parameters to translate contents.
+ *
+ *
+ * $translate('HEADLINE_TEXT').then(function (translation) {
+ * $scope.translatedText = translation;
+ * });
+ *
+ *
+ * @param {string|array} translationId A token which represents a translation id
+ * This can be optionally an array of translation ids which
+ * results that the function returns an object where each key
+ * is the translation id and the value the translation.
+ * @param {object=} interpolateParams An object hash for dynamic values
+ * @param {string} interpolationId The id of the interpolation to use
+ * @param {string} defaultTranslationText the optional default translation text that is written as
+ * as default text in case it is not found in any configured language
+ * @param {string} forceLanguage A language to be used instead of the current language
+ * @returns {object} promise
+ */
+ this.$get = ['$log', '$injector', '$rootScope', '$q', function ($log, $injector, $rootScope, $q) {
+
+ var Storage,
+ defaultInterpolator = $injector.get($interpolationFactory || '$translateDefaultInterpolation'),
+ pendingLoader = false,
+ interpolatorHashMap = {},
+ langPromises = {},
+ fallbackIndex,
+ startFallbackIteration;
+
+ var $translate = function (translationId, interpolateParams, interpolationId, defaultTranslationText, forceLanguage) {
+ if (!$uses && $preferredLanguage) {
+ $uses = $preferredLanguage;
+ }
+ var uses = (forceLanguage && forceLanguage !== $uses) ? // we don't want to re-negotiate $uses
+ (negotiateLocale(forceLanguage) || forceLanguage) : $uses;
+
+ // Check forceLanguage is present
+ if (forceLanguage) {
+ loadTranslationsIfMissing(forceLanguage);
+ }
+
+ // Duck detection: If the first argument is an array, a bunch of translations was requested.
+ // The result is an object.
+ if (angular.isArray(translationId)) {
+ // Inspired by Q.allSettled by Kris Kowal
+ // https://github.com/kriskowal/q/blob/b0fa72980717dc202ffc3cbf03b936e10ebbb9d7/q.js#L1553-1563
+ // This transforms all promises regardless resolved or rejected
+ var translateAll = function (translationIds) {
+ var results = {}; // storing the actual results
+ var promises = []; // promises to wait for
+ // Wraps the promise a) being always resolved and b) storing the link id->value
+ var translate = function (translationId) {
+ var deferred = $q.defer();
+ var regardless = function (value) {
+ results[translationId] = value;
+ deferred.resolve([translationId, value]);
+ };
+ // we don't care whether the promise was resolved or rejected; just store the values
+ $translate(translationId, interpolateParams, interpolationId, defaultTranslationText, forceLanguage).then(regardless, regardless);
+ return deferred.promise;
+ };
+ for (var i = 0, c = translationIds.length; i < c; i++) {
+ promises.push(translate(translationIds[i]));
+ }
+ // wait for all (including storing to results)
+ return $q.all(promises).then(function () {
+ // return the results
+ return results;
+ });
+ };
+ return translateAll(translationId);
+ }
+
+ var deferred = $q.defer();
+
+ // trim off any whitespace
+ if (translationId) {
+ translationId = trim.apply(translationId);
+ }
+
+ var promiseToWaitFor = (function () {
+ var promise = $preferredLanguage ?
+ langPromises[$preferredLanguage] :
+ langPromises[uses];
+
+ fallbackIndex = 0;
+
+ if ($storageFactory && !promise) {
+ // looks like there's no pending promise for $preferredLanguage or
+ // $uses. Maybe there's one pending for a language that comes from
+ // storage.
+ var langKey = Storage.get($storageKey);
+ promise = langPromises[langKey];
+
+ if ($fallbackLanguage && $fallbackLanguage.length) {
+ var index = indexOf($fallbackLanguage, langKey);
+ // maybe the language from storage is also defined as fallback language
+ // we increase the fallback language index to not search in that language
+ // as fallback, since it's probably the first used language
+ // in that case the index starts after the first element
+ fallbackIndex = (index === 0) ? 1 : 0;
+
+ // but we can make sure to ALWAYS fallback to preferred language at least
+ if (indexOf($fallbackLanguage, $preferredLanguage) < 0) {
+ $fallbackLanguage.push($preferredLanguage);
+ }
+ }
+ }
+ return promise;
+ }());
+
+ if (!promiseToWaitFor) {
+ // no promise to wait for? okay. Then there's no loader registered
+ // nor is a one pending for language that comes from storage.
+ // We can just translate.
+ determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText, uses).then(deferred.resolve, deferred.reject);
+ } else {
+ var promiseResolved = function () {
+ // $uses may have changed while waiting
+ if (!forceLanguage) {
+ uses = $uses;
+ }
+ determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText, uses).then(deferred.resolve, deferred.reject);
+ };
+ promiseResolved.displayName = 'promiseResolved';
+
+ promiseToWaitFor['finally'](promiseResolved)
+ .catch(angular.noop); // we don't care about errors here, already handled
+ }
+ return deferred.promise;
+ };
+
+ /**
+ * @name applyNotFoundIndicators
+ * @private
+ *
+ * @description
+ * Applies not fount indicators to given translation id, if needed.
+ * This function gets only executed, if a translation id doesn't exist,
+ * which is why a translation id is expected as argument.
+ *
+ * @param {string} translationId Translation id.
+ * @returns {string} Same as given translation id but applied with not found
+ * indicators.
+ */
+ var applyNotFoundIndicators = function (translationId) {
+ // applying notFoundIndicators
+ if ($notFoundIndicatorLeft) {
+ translationId = [$notFoundIndicatorLeft, translationId].join(' ');
+ }
+ if ($notFoundIndicatorRight) {
+ translationId = [translationId, $notFoundIndicatorRight].join(' ');
+ }
+ return translationId;
+ };
+
+ /**
+ * @name useLanguage
+ * @private
+ *
+ * @description
+ * Makes actual use of a language by setting a given language key as used
+ * language and informs registered interpolators to also use the given
+ * key as locale.
+ *
+ * @param {string} key Locale key.
+ */
+ var useLanguage = function (key) {
+ $uses = key;
+
+ // make sure to store new language key before triggering success event
+ if ($storageFactory) {
+ Storage.put($translate.storageKey(), $uses);
+ }
+
+ $rootScope.$emit('$translateChangeSuccess', {language : key});
+
+ // inform default interpolator
+ defaultInterpolator.setLocale($uses);
+
+ var eachInterpolator = function (interpolator, id) {
+ interpolatorHashMap[id].setLocale($uses);
+ };
+ eachInterpolator.displayName = 'eachInterpolatorLocaleSetter';
+
+ // inform all others too!
+ angular.forEach(interpolatorHashMap, eachInterpolator);
+ $rootScope.$emit('$translateChangeEnd', {language : key});
+ };
+
+ /**
+ * @name loadAsync
+ * @private
+ *
+ * @description
+ * Kicks off registered async loader using `$injector` and applies existing
+ * loader options. When resolved, it updates translation tables accordingly
+ * or rejects with given language key.
+ *
+ * @param {string} key Language key.
+ * @return {Promise} A promise.
+ */
+ var loadAsync = function (key) {
+ if (!key) {
+ throw 'No language key specified for loading.';
+ }
+
+ var deferred = $q.defer();
+
+ $rootScope.$emit('$translateLoadingStart', {language : key});
+ pendingLoader = true;
+
+ var cache = loaderCache;
+ if (typeof(cache) === 'string') {
+ // getting on-demand instance of loader
+ cache = $injector.get(cache);
+ }
+
+ var loaderOptions = angular.extend({}, $loaderOptions, {
+ key : key,
+ $http : angular.extend({}, {
+ cache : cache
+ }, $loaderOptions.$http)
+ });
+
+ var onLoaderSuccess = function (data) {
+ var translationTable = {};
+ $rootScope.$emit('$translateLoadingSuccess', {language : key});
+
+ if (angular.isArray(data)) {
+ angular.forEach(data, function (table) {
+ angular.extend(translationTable, flatObject(table));
+ });
+ } else {
+ angular.extend(translationTable, flatObject(data));
+ }
+ pendingLoader = false;
+ deferred.resolve({
+ key : key,
+ table : translationTable
+ });
+ $rootScope.$emit('$translateLoadingEnd', {language : key});
+ };
+ onLoaderSuccess.displayName = 'onLoaderSuccess';
+
+ var onLoaderError = function (key) {
+ $rootScope.$emit('$translateLoadingError', {language : key});
+ deferred.reject(key);
+ $rootScope.$emit('$translateLoadingEnd', {language : key});
+ };
+ onLoaderError.displayName = 'onLoaderError';
+
+ $injector.get($loaderFactory)(loaderOptions)
+ .then(onLoaderSuccess, onLoaderError);
+
+ return deferred.promise;
+ };
+
+ if ($storageFactory) {
+ Storage = $injector.get($storageFactory);
+
+ if (!Storage.get || !Storage.put) {
+ throw new Error('Couldn\'t use storage \'' + $storageFactory + '\', missing get() or put() method!');
+ }
+ }
+
+ // if we have additional interpolations that were added via
+ // $translateProvider.addInterpolation(), we have to map'em
+ if ($interpolatorFactories.length) {
+ var eachInterpolationFactory = function (interpolatorFactory) {
+ var interpolator = $injector.get(interpolatorFactory);
+ // setting initial locale for each interpolation service
+ interpolator.setLocale($preferredLanguage || $uses);
+ // make'em recognizable through id
+ interpolatorHashMap[interpolator.getInterpolationIdentifier()] = interpolator;
+ };
+ eachInterpolationFactory.displayName = 'interpolationFactoryAdder';
+
+ angular.forEach($interpolatorFactories, eachInterpolationFactory);
+ }
+
+ /**
+ * @name getTranslationTable
+ * @private
+ *
+ * @description
+ * Returns a promise that resolves to the translation table
+ * or is rejected if an error occurred.
+ *
+ * @param langKey
+ * @returns {Q.promise}
+ */
+ var getTranslationTable = function (langKey) {
+ var deferred = $q.defer();
+ if (Object.prototype.hasOwnProperty.call($translationTable, langKey)) {
+ deferred.resolve($translationTable[langKey]);
+ } else if (langPromises[langKey]) {
+ var onResolve = function (data) {
+ translations(data.key, data.table);
+ deferred.resolve(data.table);
+ };
+ onResolve.displayName = 'translationTableResolver';
+ langPromises[langKey].then(onResolve, deferred.reject);
+ } else {
+ deferred.reject();
+ }
+ return deferred.promise;
+ };
+
+ /**
+ * @name getFallbackTranslation
+ * @private
+ *
+ * @description
+ * Returns a promise that will resolve to the translation
+ * or be rejected if no translation was found for the language.
+ * This function is currently only used for fallback language translation.
+ *
+ * @param langKey The language to translate to.
+ * @param translationId
+ * @param interpolateParams
+ * @param Interpolator
+ * @param sanitizeStrategy
+ * @returns {Q.promise}
+ */
+ var getFallbackTranslation = function (langKey, translationId, interpolateParams, Interpolator, sanitizeStrategy) {
+ var deferred = $q.defer();
+
+ var onResolve = function (translationTable) {
+ if (Object.prototype.hasOwnProperty.call(translationTable, translationId) && translationTable[translationId] !== null) {
+ Interpolator.setLocale(langKey);
+ var translation = translationTable[translationId];
+ if (translation.substr(0, 2) === '@:') {
+ getFallbackTranslation(langKey, translation.substr(2), interpolateParams, Interpolator, sanitizeStrategy)
+ .then(deferred.resolve, deferred.reject);
+ } else {
+ var interpolatedValue = Interpolator.interpolate(translationTable[translationId], interpolateParams, 'service', sanitizeStrategy, translationId);
+ interpolatedValue = applyPostProcessing(translationId, translationTable[translationId], interpolatedValue, interpolateParams, langKey);
+
+ deferred.resolve(interpolatedValue);
+
+ }
+ Interpolator.setLocale($uses);
+ } else {
+ deferred.reject();
+ }
+ };
+ onResolve.displayName = 'fallbackTranslationResolver';
+
+ getTranslationTable(langKey).then(onResolve, deferred.reject);
+
+ return deferred.promise;
+ };
+
+ /**
+ * @name getFallbackTranslationInstant
+ * @private
+ *
+ * @description
+ * Returns a translation
+ * This function is currently only used for fallback language translation.
+ *
+ * @param langKey The language to translate to.
+ * @param translationId
+ * @param interpolateParams
+ * @param Interpolator
+ * @param sanitizeStrategy sanitize strategy override
+ *
+ * @returns {string} translation
+ */
+ var getFallbackTranslationInstant = function (langKey, translationId, interpolateParams, Interpolator, sanitizeStrategy) {
+ var result, translationTable = $translationTable[langKey];
+
+ if (translationTable && Object.prototype.hasOwnProperty.call(translationTable, translationId) && translationTable[translationId] !== null) {
+ Interpolator.setLocale(langKey);
+ result = Interpolator.interpolate(translationTable[translationId], interpolateParams, 'filter', sanitizeStrategy, translationId);
+ result = applyPostProcessing(translationId, translationTable[translationId], result, interpolateParams, langKey, sanitizeStrategy);
+ // workaround for TrustedValueHolderType
+ if (!angular.isString(result) && angular.isFunction(result.$$unwrapTrustedValue)) {
+ var result2 = result.$$unwrapTrustedValue();
+ if (result2.substr(0, 2) === '@:') {
+ return getFallbackTranslationInstant(langKey, result2.substr(2), interpolateParams, Interpolator, sanitizeStrategy);
+ }
+ } else if (result.substr(0, 2) === '@:') {
+ return getFallbackTranslationInstant(langKey, result.substr(2), interpolateParams, Interpolator, sanitizeStrategy);
+ }
+ Interpolator.setLocale($uses);
+ }
+
+ return result;
+ };
+
+
+ /**
+ * @name translateByHandler
+ * @private
+ *
+ * Translate by missing translation handler.
+ *
+ * @param translationId
+ * @param interpolateParams
+ * @param defaultTranslationText
+ * @param sanitizeStrategy sanitize strategy override
+ *
+ * @returns translation created by $missingTranslationHandler or translationId is $missingTranslationHandler is
+ * absent
+ */
+ var translateByHandler = function (translationId, interpolateParams, defaultTranslationText, sanitizeStrategy) {
+ // If we have a handler factory - we might also call it here to determine if it provides
+ // a default text for a translationid that can't be found anywhere in our tables
+ if ($missingTranslationHandlerFactory) {
+ return $injector.get($missingTranslationHandlerFactory)(translationId, $uses, interpolateParams, defaultTranslationText, sanitizeStrategy);
+ } else {
+ return translationId;
+ }
+ };
+
+ /**
+ * @name resolveForFallbackLanguage
+ * @private
+ *
+ * Recursive helper function for fallbackTranslation that will sequentially look
+ * for a translation in the fallbackLanguages starting with fallbackLanguageIndex.
+ *
+ * @param fallbackLanguageIndex
+ * @param translationId
+ * @param interpolateParams
+ * @param Interpolator
+ * @param defaultTranslationText
+ * @param sanitizeStrategy
+ * @returns {Q.promise} Promise that will resolve to the translation.
+ */
+ var resolveForFallbackLanguage = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator, defaultTranslationText, sanitizeStrategy) {
+ var deferred = $q.defer();
+
+ if (fallbackLanguageIndex < $fallbackLanguage.length) {
+ var langKey = $fallbackLanguage[fallbackLanguageIndex];
+ getFallbackTranslation(langKey, translationId, interpolateParams, Interpolator, sanitizeStrategy).then(
+ function (data) {
+ deferred.resolve(data);
+ },
+ function () {
+ // Look in the next fallback language for a translation.
+ // It delays the resolving by passing another promise to resolve.
+ return resolveForFallbackLanguage(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator, defaultTranslationText, sanitizeStrategy).then(deferred.resolve, deferred.reject);
+ }
+ );
+ } else {
+ // No translation found in any fallback language
+ // if a default translation text is set in the directive, then return this as a result
+ if (defaultTranslationText) {
+ deferred.resolve(defaultTranslationText);
+ } else {
+ var missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams, defaultTranslationText);
+
+ // if no default translation is set and an error handler is defined, send it to the handler
+ // and then return the result if it isn't undefined
+ if ($missingTranslationHandlerFactory && missingTranslationHandlerTranslation) {
+ deferred.resolve(missingTranslationHandlerTranslation);
+ } else {
+ deferred.reject(applyNotFoundIndicators(translationId));
+ }
+ }
+ }
+ return deferred.promise;
+ };
+
+ /**
+ * @name resolveForFallbackLanguageInstant
+ * @private
+ *
+ * Recursive helper function for fallbackTranslation that will sequentially look
+ * for a translation in the fallbackLanguages starting with fallbackLanguageIndex.
+ *
+ * @param fallbackLanguageIndex
+ * @param translationId
+ * @param interpolateParams
+ * @param Interpolator
+ * @param sanitizeStrategy
+ * @returns {string} translation
+ */
+ var resolveForFallbackLanguageInstant = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator, sanitizeStrategy) {
+ var result;
+
+ if (fallbackLanguageIndex < $fallbackLanguage.length) {
+ var langKey = $fallbackLanguage[fallbackLanguageIndex];
+ result = getFallbackTranslationInstant(langKey, translationId, interpolateParams, Interpolator, sanitizeStrategy);
+ if (!result && result !== '') {
+ result = resolveForFallbackLanguageInstant(fallbackLanguageIndex + 1, translationId, interpolateParams, Interpolator);
+ }
+ }
+ return result;
+ };
+
+ /**
+ * Translates with the usage of the fallback languages.
+ *
+ * @param translationId
+ * @param interpolateParams
+ * @param Interpolator
+ * @param defaultTranslationText
+ * @param sanitizeStrategy
+ * @returns {Q.promise} Promise, that resolves to the translation.
+ */
+ var fallbackTranslation = function (translationId, interpolateParams, Interpolator, defaultTranslationText, sanitizeStrategy) {
+ // Start with the fallbackLanguage with index 0
+ return resolveForFallbackLanguage((startFallbackIteration > 0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator, defaultTranslationText, sanitizeStrategy);
+ };
+
+ /**
+ * Translates with the usage of the fallback languages.
+ *
+ * @param translationId
+ * @param interpolateParams
+ * @param Interpolator
+ * @param sanitizeStrategy
+ * @returns {String} translation
+ */
+ var fallbackTranslationInstant = function (translationId, interpolateParams, Interpolator, sanitizeStrategy) {
+ // Start with the fallbackLanguage with index 0
+ return resolveForFallbackLanguageInstant((startFallbackIteration > 0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator, sanitizeStrategy);
+ };
+
+ var determineTranslation = function (translationId, interpolateParams, interpolationId, defaultTranslationText, uses, sanitizeStrategy) {
+
+ var deferred = $q.defer();
+
+ var table = uses ? $translationTable[uses] : $translationTable,
+ Interpolator = (interpolationId) ? interpolatorHashMap[interpolationId] : defaultInterpolator;
+
+ // if the translation id exists, we can just interpolate it
+ if (table && Object.prototype.hasOwnProperty.call(table, translationId) && table[translationId] !== null) {
+ var translation = table[translationId];
+
+ // If using link, rerun $translate with linked translationId and return it
+ if (translation.substr(0, 2) === '@:') {
+
+ $translate(translation.substr(2), interpolateParams, interpolationId, defaultTranslationText, uses)
+ .then(deferred.resolve, deferred.reject);
+ } else {
+ //
+ var resolvedTranslation = Interpolator.interpolate(translation, interpolateParams, 'service', sanitizeStrategy, translationId);
+ resolvedTranslation = applyPostProcessing(translationId, translation, resolvedTranslation, interpolateParams, uses);
+ deferred.resolve(resolvedTranslation);
+ }
+ } else {
+ var missingTranslationHandlerTranslation;
+ // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise
+ if ($missingTranslationHandlerFactory && !pendingLoader) {
+ missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams, defaultTranslationText);
+ }
+
+ // since we couldn't translate the inital requested translation id,
+ // we try it now with one or more fallback languages, if fallback language(s) is
+ // configured.
+ if (uses && $fallbackLanguage && $fallbackLanguage.length) {
+ fallbackTranslation(translationId, interpolateParams, Interpolator, defaultTranslationText, sanitizeStrategy)
+ .then(function (translation) {
+ deferred.resolve(translation);
+ }, function (_translationId) {
+ deferred.reject(applyNotFoundIndicators(_translationId));
+ });
+ } else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) {
+ // looks like the requested translation id doesn't exists.
+ // Now, if there is a registered handler for missing translations and no
+ // asyncLoader is pending, we execute the handler
+ if (defaultTranslationText) {
+ deferred.resolve(defaultTranslationText);
+ } else {
+ deferred.resolve(missingTranslationHandlerTranslation);
+ }
+ } else {
+ if (defaultTranslationText) {
+ deferred.resolve(defaultTranslationText);
+ } else {
+ deferred.reject(applyNotFoundIndicators(translationId));
+ }
+ }
+ }
+ return deferred.promise;
+ };
+
+ var determineTranslationInstant = function (translationId, interpolateParams, interpolationId, uses, sanitizeStrategy) {
+
+ var result, table = uses ? $translationTable[uses] : $translationTable,
+ Interpolator = defaultInterpolator;
+
+ // if the interpolation id exists use custom interpolator
+ if (interpolatorHashMap && Object.prototype.hasOwnProperty.call(interpolatorHashMap, interpolationId)) {
+ Interpolator = interpolatorHashMap[interpolationId];
+ }
+
+ // if the translation id exists, we can just interpolate it
+ if (table && Object.prototype.hasOwnProperty.call(table, translationId) && table[translationId] !== null) {
+ var translation = table[translationId];
+
+ // If using link, rerun $translate with linked translationId and return it
+ if (translation.substr(0, 2) === '@:') {
+ result = determineTranslationInstant(translation.substr(2), interpolateParams, interpolationId, uses, sanitizeStrategy);
+ } else {
+ result = Interpolator.interpolate(translation, interpolateParams, 'filter', sanitizeStrategy, translationId);
+ result = applyPostProcessing(translationId, translation, result, interpolateParams, uses, sanitizeStrategy);
+ }
+ } else {
+ var missingTranslationHandlerTranslation;
+ // for logging purposes only (as in $translateMissingTranslationHandlerLog), value is not returned to promise
+ if ($missingTranslationHandlerFactory && !pendingLoader) {
+ missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams, sanitizeStrategy);
+ }
+
+ // since we couldn't translate the inital requested translation id,
+ // we try it now with one or more fallback languages, if fallback language(s) is
+ // configured.
+ if (uses && $fallbackLanguage && $fallbackLanguage.length) {
+ fallbackIndex = 0;
+ result = fallbackTranslationInstant(translationId, interpolateParams, Interpolator, sanitizeStrategy);
+ } else if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) {
+ // looks like the requested translation id doesn't exists.
+ // Now, if there is a registered handler for missing translations and no
+ // asyncLoader is pending, we execute the handler
+ result = missingTranslationHandlerTranslation;
+ } else {
+ result = applyNotFoundIndicators(translationId);
+ }
+ }
+
+ return result;
+ };
+
+ var clearNextLangAndPromise = function (key) {
+ if ($nextLang === key) {
+ $nextLang = undefined;
+ }
+ langPromises[key] = undefined;
+ };
+
+ var applyPostProcessing = function (translationId, translation, resolvedTranslation, interpolateParams, uses, sanitizeStrategy) {
+ var fn = postProcessFn;
+
+ if (fn) {
+
+ if (typeof(fn) === 'string') {
+ // getting on-demand instance
+ fn = $injector.get(fn);
+ }
+ if (fn) {
+ return fn(translationId, translation, resolvedTranslation, interpolateParams, uses, sanitizeStrategy);
+ }
+ }
+
+ return resolvedTranslation;
+ };
+
+ var loadTranslationsIfMissing = function (key) {
+ if (!$translationTable[key] && $loaderFactory && !langPromises[key]) {
+ langPromises[key] = loadAsync(key).then(function (translation) {
+ translations(translation.key, translation.table);
+ return translation;
+ });
+ }
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#preferredLanguage
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Returns the language key for the preferred language.
+ *
+ * @param {string} langKey language String or Array to be used as preferredLanguage (changing at runtime)
+ *
+ * @return {string} preferred language key
+ */
+ $translate.preferredLanguage = function (langKey) {
+ if (langKey) {
+ setupPreferredLanguage(langKey);
+ }
+ return $preferredLanguage;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#cloakClassName
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Returns the configured class name for `translate-cloak` directive.
+ *
+ * @return {string} cloakClassName
+ */
+ $translate.cloakClassName = function () {
+ return $cloakClassName;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#nestedObjectDelimeter
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Returns the configured delimiter for nested namespaces.
+ *
+ * @return {string} nestedObjectDelimeter
+ */
+ $translate.nestedObjectDelimeter = function () {
+ return $nestedObjectDelimeter;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#fallbackLanguage
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Returns the language key for the fallback languages or sets a new fallback stack.
+ *
+ * @param {string=} langKey language String or Array of fallback languages to be used (to change stack at runtime)
+ *
+ * @return {string||array} fallback language key
+ */
+ $translate.fallbackLanguage = function (langKey) {
+ if (langKey !== undefined && langKey !== null) {
+ fallbackStack(langKey);
+
+ // as we might have an async loader initiated and a new translation language might have been defined
+ // we need to add the promise to the stack also. So - iterate.
+ if ($loaderFactory) {
+ if ($fallbackLanguage && $fallbackLanguage.length) {
+ for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
+ if (!langPromises[$fallbackLanguage[i]]) {
+ langPromises[$fallbackLanguage[i]] = loadAsync($fallbackLanguage[i]);
+ }
+ }
+ }
+ }
+ $translate.use($translate.use());
+ }
+ if ($fallbackWasString) {
+ return $fallbackLanguage[0];
+ } else {
+ return $fallbackLanguage;
+ }
+
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#useFallbackLanguage
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Sets the first key of the fallback language stack to be used for translation.
+ * Therefore all languages in the fallback array BEFORE this key will be skipped!
+ *
+ * @param {string=} langKey Contains the langKey the iteration shall start with. Set to false if you want to
+ * get back to the whole stack
+ */
+ $translate.useFallbackLanguage = function (langKey) {
+ if (langKey !== undefined && langKey !== null) {
+ if (!langKey) {
+ startFallbackIteration = 0;
+ } else {
+ var langKeyPosition = indexOf($fallbackLanguage, langKey);
+ if (langKeyPosition > -1) {
+ startFallbackIteration = langKeyPosition;
+ }
+ }
+
+ }
+
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#proposedLanguage
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Returns the language key of language that is currently loaded asynchronously.
+ *
+ * @return {string} language key
+ */
+ $translate.proposedLanguage = function () {
+ return $nextLang;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#storage
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Returns registered storage.
+ *
+ * @return {object} Storage
+ */
+ $translate.storage = function () {
+ return Storage;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#negotiateLocale
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Returns a language key based on available languages and language aliases. If a
+ * language key cannot be resolved, returns undefined.
+ *
+ * If no or a falsy key is given, returns undefined.
+ *
+ * @param {string} [key] Language key
+ * @return {string|undefined} Language key or undefined if no language key is found.
+ */
+ $translate.negotiateLocale = negotiateLocale;
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#use
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Tells angular-translate which language to use by given language key. This method is
+ * used to change language at runtime. It also takes care of storing the language
+ * key in a configured store to let your app remember the choosed language.
+ *
+ * When trying to 'use' a language which isn't available it tries to load it
+ * asynchronously with registered loaders.
+ *
+ * Returns promise object with loaded language file data or string of the currently used language.
+ *
+ * If no or a falsy key is given it returns the currently used language key.
+ * The returned string will be ```undefined``` if setting up $translate hasn't finished.
+ * @example
+ * $translate.use("en_US").then(function(data){
+ * $scope.text = $translate("HELLO");
+ * });
+ *
+ * @param {string} [key] Language key
+ * @return {object|string} Promise with loaded language data or the language key if a falsy param was given.
+ */
+ $translate.use = function (key) {
+ if (!key) {
+ return $uses;
+ }
+
+ var deferred = $q.defer();
+ deferred.promise.then(null, angular.noop); // AJS "Possibly unhandled rejection"
+
+ $rootScope.$emit('$translateChangeStart', {language : key});
+
+ // Try to get the aliased language key
+ var aliasedKey = negotiateLocale(key);
+ // Ensure only registered language keys will be loaded
+ if ($availableLanguageKeys.length > 0 && !aliasedKey) {
+ return $q.reject(key);
+ }
+
+ if (aliasedKey) {
+ key = aliasedKey;
+ }
+
+ // if there isn't a translation table for the language we've requested,
+ // we load it asynchronously
+ $nextLang = key;
+ if (($forceAsyncReloadEnabled || !$translationTable[key]) && $loaderFactory && !langPromises[key]) {
+ langPromises[key] = loadAsync(key).then(function (translation) {
+ translations(translation.key, translation.table);
+ deferred.resolve(translation.key);
+ if ($nextLang === key) {
+ useLanguage(translation.key);
+ }
+ return translation;
+ }, function (key) {
+ $rootScope.$emit('$translateChangeError', {language : key});
+ deferred.reject(key);
+ $rootScope.$emit('$translateChangeEnd', {language : key});
+ return $q.reject(key);
+ });
+ langPromises[key]['finally'](function () {
+ clearNextLangAndPromise(key);
+ }).catch(angular.noop); // we don't care about errors (clearing)
+ } else if (langPromises[key]) {
+ // we are already loading this asynchronously
+ // resolve our new deferred when the old langPromise is resolved
+ langPromises[key].then(function (translation) {
+ if ($nextLang === translation.key) {
+ useLanguage(translation.key);
+ }
+ deferred.resolve(translation.key);
+ return translation;
+ }, function (key) {
+ // find first available fallback language if that request has failed
+ if (!$uses && $fallbackLanguage && $fallbackLanguage.length > 0 && $fallbackLanguage[0] !== key) {
+ return $translate.use($fallbackLanguage[0]).then(deferred.resolve, deferred.reject);
+ } else {
+ return deferred.reject(key);
+ }
+ });
+ } else {
+ deferred.resolve(key);
+ useLanguage(key);
+ }
+
+ return deferred.promise;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#resolveClientLocale
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * This returns the current browser/client's language key. The result is processed with the configured uniform tag resolver.
+ *
+ * @returns {string} the current client/browser language key
+ */
+ $translate.resolveClientLocale = function () {
+ return getLocale();
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#storageKey
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Returns the key for the storage.
+ *
+ * @return {string} storage key
+ */
+ $translate.storageKey = function () {
+ return storageKey();
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#isPostCompilingEnabled
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Returns whether post compiling is enabled or not
+ *
+ * @return {bool} storage key
+ */
+ $translate.isPostCompilingEnabled = function () {
+ return $postCompilingEnabled;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#isForceAsyncReloadEnabled
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Returns whether force async reload is enabled or not
+ *
+ * @return {boolean} forceAsyncReload value
+ */
+ $translate.isForceAsyncReloadEnabled = function () {
+ return $forceAsyncReloadEnabled;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#isKeepContent
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Returns whether keepContent or not
+ *
+ * @return {boolean} keepContent value
+ */
+ $translate.isKeepContent = function () {
+ return $keepContent;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#refresh
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Refreshes a translation table pointed by the given langKey. If langKey is not specified,
+ * the module will drop all existent translation tables and load new version of those which
+ * are currently in use.
+ *
+ * Refresh means that the module will drop target translation table and try to load it again.
+ *
+ * In case there are no loaders registered the refresh() method will throw an Error.
+ *
+ * If the module is able to refresh translation tables refresh() method will broadcast
+ * $translateRefreshStart and $translateRefreshEnd events.
+ *
+ * @example
+ * // this will drop all currently existent translation tables and reload those which are
+ * // currently in use
+ * $translate.refresh();
+ * // this will refresh a translation table for the en_US language
+ * $translate.refresh('en_US');
+ *
+ * @param {string} langKey A language key of the table, which has to be refreshed
+ *
+ * @return {promise} Promise, which will be resolved in case a translation tables refreshing
+ * process is finished successfully, and reject if not.
+ */
+ $translate.refresh = function (langKey) {
+ if (!$loaderFactory) {
+ throw new Error('Couldn\'t refresh translation table, no loader registered!');
+ }
+
+ $rootScope.$emit('$translateRefreshStart', {language : langKey});
+
+ var deferred = $q.defer(), updatedLanguages = {};
+
+ //private helper
+ function loadNewData(languageKey) {
+ var promise = loadAsync(languageKey);
+ //update the load promise cache for this language
+ langPromises[languageKey] = promise;
+ //register a data handler for the promise
+ promise.then(function (data) {
+ //clear the cache for this language
+ $translationTable[languageKey] = {};
+ //add the new data for this language
+ translations(languageKey, data.table);
+ //track that we updated this language
+ updatedLanguages[languageKey] = true;
+ },
+ //handle rejection to appease the $q validation
+ angular.noop);
+ return promise;
+ }
+
+ //set up post-processing
+ deferred.promise.then(
+ function () {
+ for (var key in $translationTable) {
+ if ($translationTable.hasOwnProperty(key)) {
+ //delete cache entries that were not updated
+ if (!(key in updatedLanguages)) {
+ delete $translationTable[key];
+ }
+ }
+ }
+ if ($uses) {
+ useLanguage($uses);
+ }
+ },
+ //handle rejection to appease the $q validation
+ angular.noop
+ ).finally(
+ function () {
+ $rootScope.$emit('$translateRefreshEnd', {language : langKey});
+ }
+ );
+
+ if (!langKey) {
+ // if there's no language key specified we refresh ALL THE THINGS!
+ var languagesToReload = $fallbackLanguage && $fallbackLanguage.slice() || [];
+ if ($uses && languagesToReload.indexOf($uses) === -1) {
+ languagesToReload.push($uses);
+ }
+ $q.all(languagesToReload.map(loadNewData)).then(deferred.resolve, deferred.reject);
+
+ } else if ($translationTable[langKey]) {
+ //just refresh the specified language cache
+ loadNewData(langKey).then(deferred.resolve, deferred.reject);
+
+ } else {
+ deferred.reject();
+ }
+
+ return deferred.promise;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#instant
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Returns a translation instantly from the internal state of loaded translation. All rules
+ * regarding the current language, the preferred language of even fallback languages will be
+ * used except any promise handling. If a language was not found, an asynchronous loading
+ * will be invoked in the background.
+ *
+ * @param {string|array} translationId A token which represents a translation id
+ * This can be optionally an array of translation ids which
+ * results that the function's promise returns an object where
+ * each key is the translation id and the value the translation.
+ * @param {object} interpolateParams Params
+ * @param {string} interpolationId The id of the interpolation to use
+ * @param {string} forceLanguage A language to be used instead of the current language
+ * @param {string} sanitizeStrategy force sanitize strategy for this call instead of using the configured one
+ *
+ * @return {string|object} translation
+ */
+ $translate.instant = function (translationId, interpolateParams, interpolationId, forceLanguage, sanitizeStrategy) {
+
+ // we don't want to re-negotiate $uses
+ var uses = (forceLanguage && forceLanguage !== $uses) ? // we don't want to re-negotiate $uses
+ (negotiateLocale(forceLanguage) || forceLanguage) : $uses;
+
+ // Detect undefined and null values to shorten the execution and prevent exceptions
+ if (translationId === null || angular.isUndefined(translationId)) {
+ return translationId;
+ }
+
+ // Check forceLanguage is present
+ if (forceLanguage) {
+ loadTranslationsIfMissing(forceLanguage);
+ }
+
+ // Duck detection: If the first argument is an array, a bunch of translations was requested.
+ // The result is an object.
+ if (angular.isArray(translationId)) {
+ var results = {};
+ for (var i = 0, c = translationId.length; i < c; i++) {
+ results[translationId[i]] = $translate.instant(translationId[i], interpolateParams, interpolationId, forceLanguage, sanitizeStrategy);
+ }
+ return results;
+ }
+
+ // We discarded unacceptable values. So we just need to verify if translationId is empty String
+ if (angular.isString(translationId) && translationId.length < 1) {
+ return translationId;
+ }
+
+ // trim off any whitespace
+ if (translationId) {
+ translationId = trim.apply(translationId);
+ }
+
+ var result, possibleLangKeys = [];
+ if ($preferredLanguage) {
+ possibleLangKeys.push($preferredLanguage);
+ }
+ if (uses) {
+ possibleLangKeys.push(uses);
+ }
+ if ($fallbackLanguage && $fallbackLanguage.length) {
+ possibleLangKeys = possibleLangKeys.concat($fallbackLanguage);
+ }
+ for (var j = 0, d = possibleLangKeys.length; j < d; j++) {
+ var possibleLangKey = possibleLangKeys[j];
+ if ($translationTable[possibleLangKey]) {
+ if (typeof $translationTable[possibleLangKey][translationId] !== 'undefined') {
+ result = determineTranslationInstant(translationId, interpolateParams, interpolationId, uses, sanitizeStrategy);
+ }
+ }
+ if (typeof result !== 'undefined') {
+ break;
+ }
+ }
+
+ if (!result && result !== '') {
+ if ($notFoundIndicatorLeft || $notFoundIndicatorRight) {
+ result = applyNotFoundIndicators(translationId);
+ } else {
+ // Return translation of default interpolator if not found anything.
+ result = defaultInterpolator.interpolate(translationId, interpolateParams, 'filter', sanitizeStrategy);
+
+ // looks like the requested translation id doesn't exists.
+ // Now, if there is a registered handler for missing translations and no
+ // asyncLoader is pending, we execute the handler
+ var missingTranslationHandlerTranslation;
+ if ($missingTranslationHandlerFactory && !pendingLoader) {
+ missingTranslationHandlerTranslation = translateByHandler(translationId, interpolateParams, sanitizeStrategy);
+ }
+
+ if ($missingTranslationHandlerFactory && !pendingLoader && missingTranslationHandlerTranslation) {
+ result = missingTranslationHandlerTranslation;
+ }
+ }
+ }
+
+ return result;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#versionInfo
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Returns the current version information for the angular-translate library
+ *
+ * @return {string} angular-translate version
+ */
+ $translate.versionInfo = function () {
+ return version;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#loaderCache
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Returns the defined loaderCache.
+ *
+ * @return {boolean|string|object} current value of loaderCache
+ */
+ $translate.loaderCache = function () {
+ return loaderCache;
+ };
+
+ // internal purpose only
+ $translate.directivePriority = function () {
+ return directivePriority;
+ };
+
+ // internal purpose only
+ $translate.statefulFilter = function () {
+ return statefulFilter;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#isReady
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Returns whether the service is "ready" to translate (i.e. loading 1st language).
+ *
+ * See also {@link pascalprecht.translate.$translate#methods_onReady onReady()}.
+ *
+ * @return {boolean} current value of ready
+ */
+ $translate.isReady = function () {
+ return $isReady;
+ };
+
+ var $onReadyDeferred = $q.defer();
+ $onReadyDeferred.promise.then(function () {
+ $isReady = true;
+ });
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#onReady
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Calls the function provided or resolved the returned promise after the service is "ready" to translate (i.e. loading 1st language).
+ *
+ * See also {@link pascalprecht.translate.$translate#methods_isReady isReady()}.
+ *
+ * @param {Function=} fn Function to invoke when service is ready
+ * @return {object} Promise resolved when service is ready
+ */
+ $translate.onReady = function (fn) {
+ var deferred = $q.defer();
+ if (angular.isFunction(fn)) {
+ deferred.promise.then(fn);
+ }
+ if ($isReady) {
+ deferred.resolve();
+ } else {
+ $onReadyDeferred.promise.then(deferred.resolve);
+ }
+ return deferred.promise;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#getAvailableLanguageKeys
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * This function simply returns the registered language keys being defined before in the config phase
+ * With this, an application can use the array to provide a language selection dropdown or similar
+ * without any additional effort
+ *
+ * @returns {object} returns the list of possibly registered language keys and mapping or null if not defined
+ */
+ $translate.getAvailableLanguageKeys = function () {
+ if ($availableLanguageKeys.length > 0) {
+ return $availableLanguageKeys;
+ }
+ return null;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translate#getTranslationTable
+ * @methodOf pascalprecht.translate.$translate
+ *
+ * @description
+ * Returns translation table by the given language key.
+ *
+ * Unless a language is provided it returns a translation table of the current one.
+ * Note: If translation dictionary is currently downloading or in progress
+ * it will return null.
+ *
+ * @param {string} langKey A token which represents a translation id
+ *
+ * @return {object} a copy of angular-translate $translationTable
+ */
+ $translate.getTranslationTable = function (langKey) {
+ langKey = langKey || $translate.use();
+ if (langKey && $translationTable[langKey]) {
+ return angular.copy($translationTable[langKey]);
+ }
+ return null;
+ };
+
+ // Whenever $translateReady is being fired, this will ensure the state of $isReady
+ var globalOnReadyListener = $rootScope.$on('$translateReady', function () {
+ $onReadyDeferred.resolve();
+ globalOnReadyListener(); // one time only
+ globalOnReadyListener = null;
+ });
+ var globalOnChangeListener = $rootScope.$on('$translateChangeEnd', function () {
+ $onReadyDeferred.resolve();
+ globalOnChangeListener(); // one time only
+ globalOnChangeListener = null;
+ });
+
+ if ($loaderFactory) {
+
+ // If at least one async loader is defined and there are no
+ // (default) translations available we should try to load them.
+ if (angular.equals($translationTable, {})) {
+ if ($translate.use()) {
+ $translate.use($translate.use());
+ }
+ }
+
+ // Also, if there are any fallback language registered, we start
+ // loading them asynchronously as soon as we can.
+ if ($fallbackLanguage && $fallbackLanguage.length) {
+ var processAsyncResult = function (translation) {
+ translations(translation.key, translation.table);
+ $rootScope.$emit('$translateChangeEnd', {language : translation.key});
+ return translation;
+ };
+ for (var i = 0, len = $fallbackLanguage.length; i < len; i++) {
+ var fallbackLanguageId = $fallbackLanguage[i];
+ if ($forceAsyncReloadEnabled || !$translationTable[fallbackLanguageId]) {
+ langPromises[fallbackLanguageId] = loadAsync(fallbackLanguageId).then(processAsyncResult);
+ }
+ }
+ }
+ } else {
+ $rootScope.$emit('$translateReady', {language : $translate.use()});
+ }
+
+ return $translate;
+ }];
+}
+
+$translate.displayName = 'displayName';
+
+/**
+ * @ngdoc object
+ * @name pascalprecht.translate.$translateDefaultInterpolation
+ * @requires $interpolate
+ *
+ * @description
+ * Uses angular's `$interpolate` services to interpolate strings against some values.
+ *
+ * Be aware to configure a proper sanitization strategy.
+ *
+ * See also:
+ * * {@link pascalprecht.translate.$translateSanitization}
+ *
+ * @return {object} $translateDefaultInterpolation Interpolator service
+ */
+angular.module('pascalprecht.translate').factory('$translateDefaultInterpolation', $translateDefaultInterpolation);
+
+function $translateDefaultInterpolation ($interpolate, $translateSanitization) {
+
+ 'use strict';
+
+ var $translateInterpolator = {},
+ $locale,
+ $identifier = 'default';
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateDefaultInterpolation#setLocale
+ * @methodOf pascalprecht.translate.$translateDefaultInterpolation
+ *
+ * @description
+ * Sets current locale (this is currently not use in this interpolation).
+ *
+ * @param {string} locale Language key or locale.
+ */
+ $translateInterpolator.setLocale = function (locale) {
+ $locale = locale;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateDefaultInterpolation#getInterpolationIdentifier
+ * @methodOf pascalprecht.translate.$translateDefaultInterpolation
+ *
+ * @description
+ * Returns an identifier for this interpolation service.
+ *
+ * @returns {string} $identifier
+ */
+ $translateInterpolator.getInterpolationIdentifier = function () {
+ return $identifier;
+ };
+
+ /**
+ * @deprecated will be removed in 3.0
+ * @see {@link pascalprecht.translate.$translateSanitization}
+ */
+ $translateInterpolator.useSanitizeValueStrategy = function (value) {
+ $translateSanitization.useStrategy(value);
+ return this;
+ };
+
+ /**
+ * @ngdoc function
+ * @name pascalprecht.translate.$translateDefaultInterpolation#interpolate
+ * @methodOf pascalprecht.translate.$translateDefaultInterpolation
+ *
+ * @description
+ * Interpolates given value agains given interpolate params using angulars
+ * `$interpolate` service.
+ *
+ * Since AngularJS 1.5, `value` must not be a string but can be anything input.
+ *
+ * @param {string} value translation
+ * @param {object} interpolationParams interpolation params
+ * @param {string} context current context (filter, directive, service)
+ * @param {string} sanitizeStrategy sanitize strategy
+ * @param {string} translationId current translationId
+ *
+ * @returns {string} interpolated string
+ */
+ $translateInterpolator.interpolate = function (value, interpolationParams, context, sanitizeStrategy, translationId) { // jshint ignore:line
+ interpolationParams = interpolationParams || {};
+ interpolationParams = $translateSanitization.sanitize(interpolationParams, 'params', sanitizeStrategy, context);
+
+ var interpolatedText;
+ if (angular.isNumber(value)) {
+ // numbers are safe
+ interpolatedText = '' + value;
+ } else if (angular.isString(value)) {
+ // strings must be interpolated (that's the job here)
+ interpolatedText = $interpolate(value)(interpolationParams);
+ interpolatedText = $translateSanitization.sanitize(interpolatedText, 'text', sanitizeStrategy, context);
+ } else {
+ // neither a number or a string, cant interpolate => empty string
+ interpolatedText = '';
+ }
+
+ return interpolatedText;
+ };
+
+ return $translateInterpolator;
+}
+
+$translateDefaultInterpolation.displayName = '$translateDefaultInterpolation';
+
+angular.module('pascalprecht.translate').constant('$STORAGE_KEY', 'NG_TRANSLATE_LANG_KEY');
+
+angular.module('pascalprecht.translate')
+/**
+ * @ngdoc directive
+ * @name pascalprecht.translate.directive:translate
+ * @requires $interpolate,
+ * @requires $compile,
+ * @requires $parse,
+ * @requires $rootScope
+ * @restrict AE
+ *
+ * @description
+ * Translates given translation id either through attribute or DOM content.
+ * Internally it uses $translate service to translate the translation id. It possible to
+ * pass an optional `translate-values` object literal as string into translation id.
+ *
+ * @param {string=} translate Translation id which could be either string or interpolated string.
+ * @param {string=} translate-values Values to pass into translation id. Can be passed as object literal string or interpolated object.
+ * @param {string=} translate-attr-ATTR translate Translation id and put it into ATTR attribute.
+ * @param {string=} translate-default will be used unless translation was successful
+ * @param {boolean=} translate-compile (default true if present) defines locally activation of {@link pascalprecht.translate.$translateProvider#methods_usePostCompiling}
+ * @param {boolean=} translate-keep-content (default true if present) defines that in case a KEY could not be translated, that the existing content is left in the innerHTML}
+ *
+ * @example
+
+
+
+
+
+
TRANSLATION_ID
+
+
+
{{translationId}}
+
+
WITH_VALUES
+
+
WITH_VALUES
+
+
+
+
+
+
+ angular.module('ngView', ['pascalprecht.translate'])
+
+ .config(function ($translateProvider) {
+
+ $translateProvider.translations('en',{
+ 'TRANSLATION_ID': 'Hello there!',
+ 'WITH_VALUES': 'The following value is dynamic: {{value}}',
+ 'WITH_CAMEL_CASE_KEY': 'The interpolation key is camel cased: {{camelCaseKey}}'
+ }).preferredLanguage('en');
+
+ });
+
+ angular.module('ngView').controller('TranslateCtrl', function ($scope) {
+ $scope.translationId = 'TRANSLATION_ID';
+
+ $scope.values = {
+ value: 78
+ };
+ });
+
+
+ it('should translate', function () {
+ inject(function ($rootScope, $compile) {
+ $rootScope.translationId = 'TRANSLATION_ID';
+
+ element = $compile('
')($rootScope);
+ $rootScope.$digest();
+ expect(element.text()).toBe('Hello there!');
+
+ element = $compile('
')($rootScope);
+ $rootScope.$digest();
+ expect(element.text()).toBe('Hello there!');
+
+ element = $compile('TRANSLATION_ID
')($rootScope);
+ $rootScope.$digest();
+ expect(element.text()).toBe('Hello there!');
+
+ element = $compile('{{translationId}}
')($rootScope);
+ $rootScope.$digest();
+ expect(element.text()).toBe('Hello there!');
+
+ element = $compile('
')($rootScope);
+ $rootScope.$digest();
+ expect(element.attr('title')).toBe('Hello there!');
+
+ element = $compile('
')($rootScope);
+ $rootScope.$digest();
+ expect(element.text()).toBe('The interpolation key is camel cased: Hello');
+ });
+ });
+
+
+ */
+.directive('translate', translateDirective);
+function translateDirective($translate, $interpolate, $compile, $parse, $rootScope) {
+
+ 'use strict';
+
+ /**
+ * @name trim
+ * @private
+ *
+ * @description
+ * trim polyfill
+ *
+ * @returns {string} The string stripped of whitespace from both ends
+ */
+ var trim = function() {
+ return this.toString().replace(/^\s+|\s+$/g, '');
+ };
+
+ return {
+ restrict: 'AE',
+ scope: true,
+ priority: $translate.directivePriority(),
+ compile: function (tElement, tAttr) {
+
+ var translateValuesExist = (tAttr.translateValues) ?
+ tAttr.translateValues : undefined;
+
+ var translateInterpolation = (tAttr.translateInterpolation) ?
+ tAttr.translateInterpolation : undefined;
+
+ var translateValueExist = tElement[0].outerHTML.match(/translate-value-+/i);
+
+ var interpolateRegExp = '^(.*)(' + $interpolate.startSymbol() + '.*' + $interpolate.endSymbol() + ')(.*)',
+ watcherRegExp = '^(.*)' + $interpolate.startSymbol() + '(.*)' + $interpolate.endSymbol() + '(.*)';
+
+ return function linkFn(scope, iElement, iAttr) {
+
+ scope.interpolateParams = {};
+ scope.preText = '';
+ scope.postText = '';
+ scope.translateNamespace = getTranslateNamespace(scope);
+ var translationIds = {};
+
+ var initInterpolationParams = function (interpolateParams, iAttr, tAttr) {
+ // initial setup
+ if (iAttr.translateValues) {
+ angular.extend(interpolateParams, $parse(iAttr.translateValues)(scope.$parent));
+ }
+ // initially fetch all attributes if existing and fill the params
+ if (translateValueExist) {
+ for (var attr in tAttr) {
+ if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') {
+ var attributeName = angular.lowercase(attr.substr(14, 1)) + attr.substr(15);
+ interpolateParams[attributeName] = tAttr[attr];
+ }
+ }
+ }
+ };
+
+ // Ensures any change of the attribute "translate" containing the id will
+ // be re-stored to the scope's "translationId".
+ // If the attribute has no content, the element's text value (white spaces trimmed off) will be used.
+ var observeElementTranslation = function (translationId) {
+
+ // Remove any old watcher
+ if (angular.isFunction(observeElementTranslation._unwatchOld)) {
+ observeElementTranslation._unwatchOld();
+ observeElementTranslation._unwatchOld = undefined;
+ }
+
+ if (angular.equals(translationId , '') || !angular.isDefined(translationId)) {
+ var iElementText = trim.apply(iElement.text());
+
+ // Resolve translation id by inner html if required
+ var interpolateMatches = iElementText.match(interpolateRegExp);
+ // Interpolate translation id if required
+ if (angular.isArray(interpolateMatches)) {
+ scope.preText = interpolateMatches[1];
+ scope.postText = interpolateMatches[3];
+ translationIds.translate = $interpolate(interpolateMatches[2])(scope.$parent);
+ var watcherMatches = iElementText.match(watcherRegExp);
+ if (angular.isArray(watcherMatches) && watcherMatches[2] && watcherMatches[2].length) {
+ observeElementTranslation._unwatchOld = scope.$watch(watcherMatches[2], function (newValue) {
+ translationIds.translate = newValue;
+ updateTranslations();
+ });
+ }
+ } else {
+ // do not assigne the translation id if it is empty.
+ translationIds.translate = !iElementText ? undefined : iElementText;
+ }
+ } else {
+ translationIds.translate = translationId;
+ }
+ updateTranslations();
+ };
+
+ var observeAttributeTranslation = function (translateAttr) {
+ iAttr.$observe(translateAttr, function (translationId) {
+ translationIds[translateAttr] = translationId;
+ updateTranslations();
+ });
+ };
+
+ // initial setup with values
+ initInterpolationParams(scope.interpolateParams, iAttr, tAttr);
+
+ var firstAttributeChangedEvent = true;
+ iAttr.$observe('translate', function (translationId) {
+ if (typeof translationId === 'undefined') {
+ // case of element "xyz "
+ observeElementTranslation('');
+ } else {
+ // case of regular attribute
+ if (translationId !== '' || !firstAttributeChangedEvent) {
+ translationIds.translate = translationId;
+ updateTranslations();
+ }
+ }
+ firstAttributeChangedEvent = false;
+ });
+
+ for (var translateAttr in iAttr) {
+ if (iAttr.hasOwnProperty(translateAttr) && translateAttr.substr(0, 13) === 'translateAttr' && translateAttr.length > 13) {
+ observeAttributeTranslation(translateAttr);
+ }
+ }
+
+ iAttr.$observe('translateDefault', function (value) {
+ scope.defaultText = value;
+ updateTranslations();
+ });
+
+ if (translateValuesExist) {
+ iAttr.$observe('translateValues', function (interpolateParams) {
+ if (interpolateParams) {
+ scope.$parent.$watch(function () {
+ angular.extend(scope.interpolateParams, $parse(interpolateParams)(scope.$parent));
+ });
+ }
+ });
+ }
+
+ if (translateValueExist) {
+ var observeValueAttribute = function (attrName) {
+ iAttr.$observe(attrName, function (value) {
+ var attributeName = angular.lowercase(attrName.substr(14, 1)) + attrName.substr(15);
+ scope.interpolateParams[attributeName] = value;
+ });
+ };
+ for (var attr in iAttr) {
+ if (Object.prototype.hasOwnProperty.call(iAttr, attr) && attr.substr(0, 14) === 'translateValue' && attr !== 'translateValues') {
+ observeValueAttribute(attr);
+ }
+ }
+ }
+
+ // Master update function
+ var updateTranslations = function () {
+ for (var key in translationIds) {
+ if (translationIds.hasOwnProperty(key) && translationIds[key] !== undefined) {
+ updateTranslation(key, translationIds[key], scope, scope.interpolateParams, scope.defaultText, scope.translateNamespace);
+ }
+ }
+ };
+
+ // Put translation processing function outside loop
+ var updateTranslation = function(translateAttr, translationId, scope, interpolateParams, defaultTranslationText, translateNamespace) {
+ if (translationId) {
+ // if translation id starts with '.' and translateNamespace given, prepend namespace
+ if (translateNamespace && translationId.charAt(0) === '.') {
+ translationId = translateNamespace + translationId;
+ }
+
+ $translate(translationId, interpolateParams, translateInterpolation, defaultTranslationText, scope.translateLanguage)
+ .then(function (translation) {
+ applyTranslation(translation, scope, true, translateAttr);
+ }, function (translationId) {
+ applyTranslation(translationId, scope, false, translateAttr);
+ });
+ } else {
+ // as an empty string cannot be translated, we can solve this using successful=false
+ applyTranslation(translationId, scope, false, translateAttr);
+ }
+ };
+
+ var applyTranslation = function (value, scope, successful, translateAttr) {
+ if (!successful) {
+ if (typeof scope.defaultText !== 'undefined') {
+ value = scope.defaultText;
+ }
+ }
+ if (translateAttr === 'translate') {
+ // default translate into innerHTML
+ if (successful || (!successful && !$translate.isKeepContent() && typeof iAttr.translateKeepContent === 'undefined')) {
+ iElement.empty().append(scope.preText + value + scope.postText);
+ }
+ var globallyEnabled = $translate.isPostCompilingEnabled();
+ var locallyDefined = typeof tAttr.translateCompile !== 'undefined';
+ var locallyEnabled = locallyDefined && tAttr.translateCompile !== 'false';
+ if ((globallyEnabled && !locallyDefined) || locallyEnabled) {
+ $compile(iElement.contents())(scope);
+ }
+ } else {
+ // translate attribute
+ var attributeName = iAttr.$attr[translateAttr];
+ if (attributeName.substr(0, 5) === 'data-') {
+ // ensure html5 data prefix is stripped
+ attributeName = attributeName.substr(5);
+ }
+ attributeName = attributeName.substr(15);
+ iElement.attr(attributeName, value);
+ }
+ };
+
+ if (translateValuesExist || translateValueExist || iAttr.translateDefault) {
+ scope.$watch('interpolateParams', updateTranslations, true);
+ }
+
+ // Replaced watcher on translateLanguage with event listener
+ scope.$on('translateLanguageChanged', updateTranslations);
+
+ // Ensures the text will be refreshed after the current language was changed
+ // w/ $translate.use(...)
+ var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations);
+
+ // ensure translation will be looked up at least one
+ if (iElement.text().length) {
+ if (iAttr.translate) {
+ observeElementTranslation(iAttr.translate);
+ } else {
+ observeElementTranslation('');
+ }
+ } else if (iAttr.translate) {
+ // ensure attribute will be not skipped
+ observeElementTranslation(iAttr.translate);
+ }
+ updateTranslations();
+ scope.$on('$destroy', unbind);
+ };
+ }
+ };
+}
+
+/**
+ * Returns the scope's namespace.
+ * @private
+ * @param scope
+ * @returns {string}
+ */
+function getTranslateNamespace(scope) {
+ 'use strict';
+ if (scope.translateNamespace) {
+ return scope.translateNamespace;
+ }
+ if (scope.$parent) {
+ return getTranslateNamespace(scope.$parent);
+ }
+}
+
+translateDirective.displayName = 'translateDirective';
+
+angular.module('pascalprecht.translate')
+/**
+ * @ngdoc directive
+ * @name pascalprecht.translate.directive:translate-attr
+ * @restrict A
+ *
+ * @description
+ * Translates attributes like translate-attr-ATTR, but with an object like ng-class.
+ * Internally it uses `translate` service to translate translation id. It possible to
+ * pass an optional `translate-values` object literal as string into translation id.
+ *
+ * @param {string=} translate-attr Object literal mapping attributes to translation ids.
+ * @param {string=} translate-values Values to pass into the translation ids. Can be passed as object literal string.
+ *
+ * @example
+
+
+
+
+
+
+
+
+
+ angular.module('ngView', ['pascalprecht.translate'])
+
+ .config(function ($translateProvider) {
+
+ $translateProvider.translations('en',{
+ 'TRANSLATION_ID': 'Hello there!',
+ 'WITH_VALUES': 'The following value is dynamic: {{value}}',
+ }).preferredLanguage('en');
+
+ });
+
+ angular.module('ngView').controller('TranslateCtrl', function ($scope) {
+ $scope.translationId = 'TRANSLATION_ID';
+
+ $scope.values = {
+ value: 78
+ };
+ });
+
+
+ it('should translate', function () {
+ inject(function ($rootScope, $compile) {
+ $rootScope.translationId = 'TRANSLATION_ID';
+
+ element = $compile(' ')($rootScope);
+ $rootScope.$digest();
+ expect(element.attr('placeholder)).toBe('Hello there!');
+ expect(element.attr('title)).toBe('The following value is dynamic: 5');
+ });
+ });
+
+
+ */
+.directive('translateAttr', translateAttrDirective);
+function translateAttrDirective($translate, $rootScope) {
+
+ 'use strict';
+
+ return {
+ restrict: 'A',
+ priority: $translate.directivePriority(),
+ link: function linkFn(scope, element, attr) {
+
+ var translateAttr,
+ translateValues,
+ previousAttributes = {};
+
+ // Main update translations function
+ var updateTranslations = function () {
+ angular.forEach(translateAttr, function (translationId, attributeName) {
+ if (!translationId) {
+ return;
+ }
+ previousAttributes[attributeName] = true;
+
+ // if translation id starts with '.' and translateNamespace given, prepend namespace
+ if (scope.translateNamespace && translationId.charAt(0) === '.') {
+ translationId = scope.translateNamespace + translationId;
+ }
+ $translate(translationId, translateValues, attr.translateInterpolation, undefined, scope.translateLanguage)
+ .then(function (translation) {
+ element.attr(attributeName, translation);
+ }, function (translationId) {
+ element.attr(attributeName, translationId);
+ });
+ });
+
+ // Removing unused attributes that were previously used
+ angular.forEach(previousAttributes, function (flag, attributeName) {
+ if (!translateAttr[attributeName]) {
+ element.removeAttr(attributeName);
+ delete previousAttributes[attributeName];
+ }
+ });
+ };
+
+ // Watch for attribute changes
+ watchAttribute(
+ scope,
+ attr.translateAttr,
+ function (newValue) { translateAttr = newValue; },
+ updateTranslations
+ );
+ // Watch for value changes
+ watchAttribute(
+ scope,
+ attr.translateValues,
+ function (newValue) { translateValues = newValue; },
+ updateTranslations
+ );
+
+ if (attr.translateValues) {
+ scope.$watch(attr.translateValues, updateTranslations, true);
+ }
+
+ // Replaced watcher on translateLanguage with event listener
+ scope.$on('translateLanguageChanged', updateTranslations);
+
+ // Ensures the text will be refreshed after the current language was changed
+ // w/ $translate.use(...)
+ var unbind = $rootScope.$on('$translateChangeSuccess', updateTranslations);
+
+ updateTranslations();
+ scope.$on('$destroy', unbind);
+ }
+ };
+}
+
+function watchAttribute(scope, attribute, valueCallback, changeCallback) {
+ 'use strict';
+ if (!attribute) {
+ return;
+ }
+ if (attribute.substr(0, 2) === '::') {
+ attribute = attribute.substr(2);
+ } else {
+ scope.$watch(attribute, function(newValue) {
+ valueCallback(newValue);
+ changeCallback();
+ }, true);
+ }
+ valueCallback(scope.$eval(attribute));
+}
+
+translateAttrDirective.displayName = 'translateAttrDirective';
+
+angular.module('pascalprecht.translate')
+/**
+ * @ngdoc directive
+ * @name pascalprecht.translate.directive:translateCloak
+ * @requires $translate
+ * @restrict A
+ *
+ * $description
+ * Adds a `translate-cloak` class name to the given element where this directive
+ * is applied initially and removes it, once a loader has finished loading.
+ *
+ * This directive can be used to prevent initial flickering when loading translation
+ * data asynchronously.
+ *
+ * The class name is defined in
+ * {@link pascalprecht.translate.$translateProvider#cloakClassName $translate.cloakClassName()}.
+ *
+ * @param {string=} translate-cloak If a translationId is provided, it will be used for showing
+ * or hiding the cloak. Basically it relies on the translation
+ * resolve.
+ */
+.directive('translateCloak', translateCloakDirective);
+
+function translateCloakDirective($translate, $rootScope) {
+
+ 'use strict';
+
+ return {
+ compile : function (tElement) {
+ var applyCloak = function (element) {
+ element.addClass($translate.cloakClassName());
+ },
+ removeCloak = function (element) {
+ element.removeClass($translate.cloakClassName());
+ };
+ applyCloak(tElement);
+
+ return function linkFn(scope, iElement, iAttr) {
+ //Create bound functions that incorporate the active DOM element.
+ var iRemoveCloak = removeCloak.bind(this, iElement), iApplyCloak = applyCloak.bind(this, iElement);
+ if (iAttr.translateCloak && iAttr.translateCloak.length) {
+ // Register a watcher for the defined translation allowing a fine tuned cloak
+ iAttr.$observe('translateCloak', function (translationId) {
+ $translate(translationId).then(iRemoveCloak, iApplyCloak);
+ });
+ $rootScope.$on('$translateChangeSuccess', function () {
+ $translate(iAttr.translateCloak).then(iRemoveCloak, iApplyCloak);
+ });
+ } else {
+ $translate.onReady(iRemoveCloak);
+ }
+ };
+ }
+ };
+}
+
+translateCloakDirective.displayName = 'translateCloakDirective';
+
+angular.module('pascalprecht.translate')
+/**
+ * @ngdoc directive
+ * @name pascalprecht.translate.directive:translateNamespace
+ * @restrict A
+ *
+ * @description
+ * Translates given translation id either through attribute or DOM content.
+ * Internally it uses `translate` filter to translate translation id. It possible to
+ * pass an optional `translate-values` object literal as string into translation id.
+ *
+ * @param {string=} translate namespace name which could be either string or interpolated string.
+ *
+ * @example
+
+
+
+
+
+
.HEADERS.TITLE
+ .HEADERS.WELCOME
+
+
+
+
.TITLE
+ .WELCOME
+
+
+
+
+
+ angular.module('ngView', ['pascalprecht.translate'])
+
+ .config(function ($translateProvider) {
+
+ $translateProvider.translations('en',{
+ 'TRANSLATION_ID': 'Hello there!',
+ 'CONTENT': {
+ 'HEADERS': {
+ TITLE: 'Title'
+ }
+ },
+ 'CONTENT.HEADERS.WELCOME': 'Welcome'
+ }).preferredLanguage('en');
+
+ });
+
+
+
+ */
+.directive('translateNamespace', translateNamespaceDirective);
+
+function translateNamespaceDirective() {
+
+ 'use strict';
+
+ return {
+ restrict: 'A',
+ scope: true,
+ compile: function () {
+ return {
+ pre: function (scope, iElement, iAttrs) {
+ scope.translateNamespace = getTranslateNamespace(scope);
+
+ if (scope.translateNamespace && iAttrs.translateNamespace.charAt(0) === '.') {
+ scope.translateNamespace += iAttrs.translateNamespace;
+ } else {
+ scope.translateNamespace = iAttrs.translateNamespace;
+ }
+ }
+ };
+ }
+ };
+}
+
+/**
+ * Returns the scope's namespace.
+ * @private
+ * @param scope
+ * @returns {string}
+ */
+function getTranslateNamespace(scope) {
+ 'use strict';
+ if (scope.translateNamespace) {
+ return scope.translateNamespace;
+ }
+ if (scope.$parent) {
+ return getTranslateNamespace(scope.$parent);
+ }
+}
+
+translateNamespaceDirective.displayName = 'translateNamespaceDirective';
+
+angular.module('pascalprecht.translate')
+/**
+ * @ngdoc directive
+ * @name pascalprecht.translate.directive:translateLanguage
+ * @restrict A
+ *
+ * @description
+ * Forces the language to the directives in the underlying scope.
+ *
+ * @param {string=} translate language that will be negotiated.
+ *
+ * @example
+
+
+
+
+
+
HELLO
+
+
+
+
HELLO
+
+
+
+
+
+ angular.module('ngView', ['pascalprecht.translate'])
+
+ .config(function ($translateProvider) {
+
+ $translateProvider
+ .translations('en',{
+ 'HELLO': 'Hello world!'
+ })
+ .translations('de',{
+ 'HELLO': 'Hallo Welt!'
+ })
+ .preferredLanguage('en');
+
+ });
+
+
+
+ */
+.directive('translateLanguage', translateLanguageDirective);
+
+function translateLanguageDirective() {
+
+ 'use strict';
+
+ return {
+ restrict: 'A',
+ scope: true,
+ compile: function () {
+ return function linkFn(scope, iElement, iAttrs) {
+
+ iAttrs.$observe('translateLanguage', function (newTranslateLanguage) {
+ scope.translateLanguage = newTranslateLanguage;
+ });
+
+ scope.$watch('translateLanguage', function(){
+ scope.$broadcast('translateLanguageChanged');
+ });
+ };
+ }
+ };
+}
+
+translateLanguageDirective.displayName = 'translateLanguageDirective';
+
+angular.module('pascalprecht.translate')
+/**
+ * @ngdoc filter
+ * @name pascalprecht.translate.filter:translate
+ * @requires $parse
+ * @requires pascalprecht.translate.$translate
+ * @function
+ *
+ * @description
+ * Uses `$translate` service to translate contents. Accepts interpolate parameters
+ * to pass dynamized values though translation.
+ *
+ * @param {string} translationId A translation id to be translated.
+ * @param {*=} interpolateParams Optional object literal (as hash or string) to pass values into translation.
+ *
+ * @returns {string} Translated text.
+ *
+ * @example
+
+
+
+
+
{{ 'TRANSLATION_ID' | translate }}
+
{{ translationId | translate }}
+
{{ 'WITH_VALUES' | translate:'{value: 5}' }}
+
{{ 'WITH_VALUES' | translate:values }}
+
+
+
+
+ angular.module('ngView', ['pascalprecht.translate'])
+
+ .config(function ($translateProvider) {
+
+ $translateProvider.translations('en', {
+ 'TRANSLATION_ID': 'Hello there!',
+ 'WITH_VALUES': 'The following value is dynamic: {{value}}'
+ });
+ $translateProvider.preferredLanguage('en');
+
+ });
+
+ angular.module('ngView').controller('TranslateCtrl', function ($scope) {
+ $scope.translationId = 'TRANSLATION_ID';
+
+ $scope.values = {
+ value: 78
+ };
+ });
+
+
+ */
+.filter('translate', translateFilterFactory);
+
+function translateFilterFactory($parse, $translate) {
+
+ 'use strict';
+
+ var translateFilter = function (translationId, interpolateParams, interpolation, forceLanguage) {
+ if (!angular.isObject(interpolateParams)) {
+ var ctx = this || {
+ '__SCOPE_IS_NOT_AVAILABLE': 'More info at https://github.com/angular/angular.js/commit/8863b9d04c722b278fa93c5d66ad1e578ad6eb1f'
+ };
+ interpolateParams = $parse(interpolateParams)(ctx);
+ }
+
+ return $translate.instant(translationId, interpolateParams, interpolation, forceLanguage);
+ };
+
+ if ($translate.statefulFilter()) {
+ translateFilter.$stateful = true;
+ }
+
+ return translateFilter;
+}
+
+translateFilterFactory.displayName = 'translateFilterFactory';
+
+angular.module('pascalprecht.translate')
+
+/**
+ * @ngdoc object
+ * @name pascalprecht.translate.$translationCache
+ * @requires $cacheFactory
+ *
+ * @description
+ * The first time a translation table is used, it is loaded in the translation cache for quick retrieval. You
+ * can load translation tables directly into the cache by consuming the
+ * `$translationCache` service directly.
+ *
+ * @return {object} $cacheFactory object.
+ */
+ .factory('$translationCache', $translationCache);
+
+function $translationCache($cacheFactory) {
+
+ 'use strict';
+
+ return $cacheFactory('translations');
+}
+
+$translationCache.displayName = '$translationCache';
+return 'pascalprecht.translate';
+
+}));
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate.min.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate.min.js
new file mode 100644
index 0000000000..8549ef9e5f
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/dist/angular-translate.min.js
@@ -0,0 +1,6 @@
+/*!
+ * angular-translate - v2.15.1 - 2017-03-04
+ *
+ * Copyright (c) 2017 The angular-translate team, Pascal Precht; Licensed MIT
+ */
+!function(a,b){"function"==typeof define&&define.amd?define([],function(){return b()}):"object"==typeof exports?module.exports=b():b()}(this,function(){function a(a){"use strict";var b=a.storageKey(),c=a.storage(),d=function(){var d=a.preferredLanguage();angular.isString(d)?a.use(d):c.put(b,a.use())};d.displayName="fallbackFromIncorrectStorageValue",c?c.get(b)?a.use(c.get(b)).catch(d):d():angular.isString(a.preferredLanguage())&&a.use(a.preferredLanguage())}function b(){"use strict";var a,b,c,d=null,e=!1,f=!1;c={sanitize:function(a,b){return"text"===b&&(a=h(a)),a},escape:function(a,b){return"text"===b&&(a=g(a)),a},sanitizeParameters:function(a,b){return"params"===b&&(a=j(a,h)),a},escapeParameters:function(a,b){return"params"===b&&(a=j(a,g)),a},sce:function(a,b,c){return"text"===b?a=i(a):"params"===b&&"filter"!==c&&(a=j(a,g)),a},sceParameters:function(a,b){return"params"===b&&(a=j(a,i)),a}},c.escaped=c.escapeParameters,this.addStrategy=function(a,b){return c[a]=b,this},this.removeStrategy=function(a){return delete c[a],this},this.useStrategy=function(a){return e=!0,d=a,this},this.$get=["$injector","$log",function(g,h){var i={},j=function(a,b,d,e){return angular.forEach(e,function(e){if(angular.isFunction(e))a=e(a,b,d);else if(angular.isFunction(c[e]))a=c[e](a,b,d);else{if(!angular.isString(c[e]))throw new Error("pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: '"+e+"'");if(!i[c[e]])try{i[c[e]]=g.get(c[e])}catch(a){throw i[c[e]]=function(){},new Error("pascalprecht.translate.$translateSanitization: Unknown sanitization strategy: '"+e+"'")}a=i[c[e]](a,b,d)}}),a},k=function(){e||f||(h.warn("pascalprecht.translate.$translateSanitization: No sanitization strategy has been configured. This can have serious security implications. See http://angular-translate.github.io/docs/#/guide/19_security for details."),f=!0)};return g.has("$sanitize")&&(a=g.get("$sanitize")),g.has("$sce")&&(b=g.get("$sce")),{useStrategy:function(a){return function(b){a.useStrategy(b)}}(this),sanitize:function(a,b,c,e){if(d||k(),c||null===c||(c=d),!c)return a;e||(e="service");var f=angular.isArray(c)?c:[c];return j(a,b,e,f)}}}];var g=function(a){var b=angular.element("
");return b.text(a),b.html()},h=function(b){if(!a)throw new Error("pascalprecht.translate.$translateSanitization: Error cannot find $sanitize service. Either include the ngSanitize module (https://docs.angularjs.org/api/ngSanitize) or use a sanitization strategy which does not depend on $sanitize, such as 'escape'.");return a(b)},i=function(a){if(!b)throw new Error("pascalprecht.translate.$translateSanitization: Error cannot find $sce service.");return b.trustAsHtml(a)},j=function(a,b,c){if(angular.isDate(a))return a;if(angular.isObject(a)){var d=angular.isArray(a)?[]:{};if(c){if(c.indexOf(a)>-1)throw new Error("pascalprecht.translate.$translateSanitization: Error cannot interpolate parameter due recursive object")}else c=[];return c.push(a),angular.forEach(a,function(a,e){angular.isFunction(a)||(d[e]=j(a,b,c))}),c.splice(-1,1),d}return angular.isNumber(a)?a:angular.isUndefined(a)||null===a?a:b(a)}}function c(a,b,c,d){"use strict";var e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u={},v=[],w=a,x=[],y="translate-cloak",z=!1,A=!1,B=".",C=!1,D=!1,E=0,F=!0,G="default",H={default:function(a){return(a||"").split("-").join("_")},java:function(a){var b=(a||"").split("-").join("_"),c=b.split("_");return c.length>1?c[0].toLowerCase()+"_"+c[1].toUpperCase():b},bcp47:function(a){var b=(a||"").split("_").join("-"),c=b.split("-");return c.length>1?c[0].toLowerCase()+"-"+c[1].toUpperCase():b},"iso639-1":function(a){var b=(a||"").split("_").join("-"),c=b.split("-");return c[0].toLowerCase()}},I="2.15.1",J=function(){if(angular.isFunction(d.getLocale))return d.getLocale();var a,c,e=b.$get().navigator,f=["language","browserLanguage","systemLanguage","userLanguage"];if(angular.isArray(e.languages))for(a=0;a-1)return a;if(f){var g;for(var h in f)if(f.hasOwnProperty(h)){var i=!1,j=Object.prototype.hasOwnProperty.call(f,h)&&angular.lowercase(h)===angular.lowercase(a);if("*"===h.slice(-1)&&(i=h.slice(0,-1)===a.slice(0,h.length-1)),(j||i)&&(g=f[h],L(b,angular.lowercase(g))>-1))return g}}var k=a.split("_");return k.length>1&&L(b,angular.lowercase(k[0]))>-1?k[0]:void 0}},O=function(a,b){if(!a&&!b)return u;if(a&&!b){if(angular.isString(a))return u[a]}else angular.isObject(u[a])||(u[a]={}),angular.extend(u[a],P(b));return this};this.translations=O,this.cloakClassName=function(a){return a?(y=a,this):y},this.nestedObjectDelimeter=function(a){return a?(B=a,this):B};var P=function(a,b,c,d){var e,f,g,h;b||(b=[]),c||(c={});for(e in a)Object.prototype.hasOwnProperty.call(a,e)&&(h=a[e],angular.isObject(h)?P(h,b.concat(e),c,e):(f=b.length?""+b.join(B)+B+e:e,b.length&&e===d&&(g=""+b.join(B),c[g]="@:"+f),c[f]=h));return c};P.displayName="flatObject",this.addInterpolation=function(a){return x.push(a),this},this.useMessageFormatInterpolation=function(){return this.useInterpolation("$translateMessageFormatInterpolation")},this.useInterpolation=function(a){return n=a,this},this.useSanitizeValueStrategy=function(a){return c.useStrategy(a),this},this.preferredLanguage=function(a){return a?(Q(a),this):e};var Q=function(a){return a&&(e=a),e};this.translationNotFoundIndicator=function(a){return this.translationNotFoundIndicatorLeft(a),this.translationNotFoundIndicatorRight(a),this},this.translationNotFoundIndicatorLeft=function(a){return a?(q=a,this):q},this.translationNotFoundIndicatorRight=function(a){return a?(r=a,this):r},this.fallbackLanguage=function(a){return R(a),this};var R=function(a){return a?(angular.isString(a)?(h=!0,g=[a]):angular.isArray(a)&&(h=!1,g=a),angular.isString(e)&&L(g,e)<0&&g.push(e),this):h?g[0]:g};this.use=function(a){if(a){if(!u[a]&&!o)throw new Error("$translateProvider couldn't find translationTable for langKey: '"+a+"'");return i=a,this}return i},this.resolveClientLocale=function(){return K()};var S=function(a){return a?(w=a,this):l?l+w:w};this.storageKey=S,this.useUrlLoader=function(a,b){return this.useLoader("$translateUrlLoader",angular.extend({url:a},b))},this.useStaticFilesLoader=function(a){return this.useLoader("$translateStaticFilesLoader",a)},this.useLoader=function(a,b){return o=a,p=b||{},this},this.useLocalStorage=function(){return this.useStorage("$translateLocalStorage")},this.useCookieStorage=function(){return this.useStorage("$translateCookieStorage")},this.useStorage=function(a){return k=a,this},this.storagePrefix=function(a){return a?(l=a,this):a},this.useMissingTranslationHandlerLog=function(){return this.useMissingTranslationHandler("$translateMissingTranslationHandlerLog")},this.useMissingTranslationHandler=function(a){return m=a,this},this.usePostCompiling=function(a){return z=!!a,this},this.forceAsyncReload=function(a){return A=!!a,this},this.uniformLanguageTag=function(a){return a?angular.isString(a)&&(a={standard:a}):a={},G=a.standard,this},this.determinePreferredLanguage=function(a){var b=a&&angular.isFunction(a)?a():K();return e=v.length?N(b)||b:b,this},this.registerAvailableLanguageKeys=function(a,b){return a?(v=a,b&&(f=b),this):v},this.useLoaderCache=function(a){return a===!1?s=void 0:a===!0?s=!0:"undefined"==typeof a?s="$translationCache":a&&(s=a),this},this.directivePriority=function(a){return void 0===a?E:(E=a,this)},this.statefulFilter=function(a){return void 0===a?F:(F=a,this)},this.postProcess=function(a){return t=a?a:void 0,this},this.keepContent=function(a){return D=!!a,this},this.$get=["$log","$injector","$rootScope","$q",function(a,b,c,d){var f,l,G,H=b.get(n||"$translateDefaultInterpolation"),J=!1,T={},U={},V=function(a,b,c,h,j){!i&&e&&(i=e);var m=j&&j!==i?N(j)||j:i;if(j&&ka(j),angular.isArray(a)){var n=function(a){for(var e={},f=[],g=function(a){var f=d.defer(),g=function(b){e[a]=b,f.resolve([a,b])};return V(a,b,c,h,j).then(g,g),f.promise},i=0,k=a.length;i0?G:l,a,b,c,d,e)},fa=function(a,b,c,d){return da(G>0?G:l,a,b,c,d)},ga=function(a,b,c,e,f,h){var i=d.defer(),j=f?u[f]:u,k=c?T[c]:H;if(j&&Object.prototype.hasOwnProperty.call(j,a)&&null!==j[a]){var l=j[a];if("@:"===l.substr(0,2))V(l.substr(2),b,c,e,f).then(i.resolve,i.reject);else{var n=k.interpolate(l,b,"service",h,a);n=ja(a,l,n,b,f),i.resolve(n)}}else{var o;m&&!J&&(o=ba(a,b,e)),f&&g&&g.length?ea(a,b,k,e,h).then(function(a){i.resolve(a)},function(a){i.reject(W(a))}):m&&!J&&o?e?i.resolve(e):i.resolve(o):e?i.resolve(e):i.reject(W(a))}return i.promise},ha=function(a,b,c,d,e){var f,h=d?u[d]:u,i=H;if(T&&Object.prototype.hasOwnProperty.call(T,c)&&(i=T[c]),h&&Object.prototype.hasOwnProperty.call(h,a)&&null!==h[a]){var j=h[a];"@:"===j.substr(0,2)?f=ha(j.substr(2),b,c,d,e):(f=i.interpolate(j,b,"filter",e,a),f=ja(a,j,f,b,d,e))}else{var k;m&&!J&&(k=ba(a,b,e)),d&&g&&g.length?(l=0,f=fa(a,b,i,e)):f=m&&!J&&k?k:W(a)}return f},ia=function(a){j===a&&(j=void 0),U[a]=void 0},ja=function(a,c,d,e,f,g){var h=t;return h&&("string"==typeof h&&(h=b.get(h)),h)?h(a,c,d,e,f,g):d},ka=function(a){u[a]||!o||U[a]||(U[a]=Y(a).then(function(a){return O(a.key,a.table),a}))};V.preferredLanguage=function(a){return a&&Q(a),e},V.cloakClassName=function(){return y},V.nestedObjectDelimeter=function(){return B},V.fallbackLanguage=function(a){if(void 0!==a&&null!==a){if(R(a),o&&g&&g.length)for(var b=0,c=g.length;b-1&&(G=b)}else G=0},V.proposedLanguage=function(){return j},V.storage=function(){return f},V.negotiateLocale=N,V.use=function(a){if(!a)return i;var b=d.defer();b.promise.then(null,angular.noop),c.$emit("$translateChangeStart",{language:a});var e=N(a);return v.length>0&&!e?d.reject(a):(e&&(a=e),j=a,!A&&u[a]||!o||U[a]?U[a]?U[a].then(function(a){return j===a.key&&X(a.key),b.resolve(a.key),a},function(a){return!i&&g&&g.length>0&&g[0]!==a?V.use(g[0]).then(b.resolve,b.reject):b.reject(a)}):(b.resolve(a),X(a)):(U[a]=Y(a).then(function(c){return O(c.key,c.table),b.resolve(c.key),j===a&&X(c.key),c},function(a){return c.$emit("$translateChangeError",{language:a}),b.reject(a),c.$emit("$translateChangeEnd",{language:a}),d.reject(a)}),U[a].finally(function(){ia(a)}).catch(angular.noop)),b.promise)},V.resolveClientLocale=function(){return K()},V.storageKey=function(){return S()},V.isPostCompilingEnabled=function(){return z},V.isForceAsyncReloadEnabled=function(){return A},V.isKeepContent=function(){return D},V.refresh=function(a){function b(a){var b=Y(a);return U[a]=b,b.then(function(b){u[a]={},O(a,b.table),f[a]=!0},angular.noop),b}if(!o)throw new Error("Couldn't refresh translation table, no loader registered!");c.$emit("$translateRefreshStart",{language:a});var e=d.defer(),f={};if(e.promise.then(function(){for(var a in u)u.hasOwnProperty(a)&&(a in f||delete u[a]);i&&X(i)},angular.noop).finally(function(){c.$emit("$translateRefreshEnd",{language:a})}),a)u[a]?b(a).then(e.resolve,e.reject):e.reject();else{var h=g&&g.slice()||[];i&&h.indexOf(i)===-1&&h.push(i),d.all(h.map(b)).then(e.resolve,e.reject)}return e.promise},V.instant=function(a,b,c,d,f){var h=d&&d!==i?N(d)||d:i;if(null===a||angular.isUndefined(a))return a;if(d&&ka(d),angular.isArray(a)){for(var j={},k=0,l=a.length;k0?v:null},V.getTranslationTable=function(a){return a=a||V.use(),a&&u[a]?angular.copy(u[a]):null};var ma=c.$on("$translateReady",function(){la.resolve(),ma(),ma=null}),na=c.$on("$translateChangeEnd",function(){la.resolve(),na(),na=null});if(o){if(angular.equals(u,{})&&V.use()&&V.use(V.use()),g&&g.length)for(var oa=function(a){return O(a.key,a.table),c.$emit("$translateChangeEnd",{language:a.key}),a},pa=0,qa=g.length;pa13&&t(v);if(p.$observe("translateDefault",function(a){h.defaultText=a,y()}),j&&p.$observe("translateValues",function(a){a&&h.$parent.$watch(function(){angular.extend(h.interpolateParams,d(a)(h.$parent))})}),l){var w=function(a){p.$observe(a,function(b){var c=angular.lowercase(a.substr(14,1))+a.substr(15);h.interpolateParams[c]=b})};for(var x in p)Object.prototype.hasOwnProperty.call(p,x)&&"translateValue"===x.substr(0,14)&&"translateValues"!==x&&w(x)}var y=function(){for(var a in q)q.hasOwnProperty(a)&&void 0!==q[a]&&z(a,q[a],h,h.interpolateParams,h.defaultText,h.translateNamespace)},z=function(b,c,d,e,f,g){c?(g&&"."===c.charAt(0)&&(c=g+c),a(c,e,k,f,d.translateLanguage).then(function(a){A(a,d,!0,b)},function(a){A(a,d,!1,b)})):A(c,d,!1,b)},A=function(b,d,e,f){if(e||"undefined"!=typeof d.defaultText&&(b=d.defaultText),"translate"===f){(e||!e&&!a.isKeepContent()&&"undefined"==typeof p.translateKeepContent)&&o.empty().append(d.preText+b+d.postText);var g=a.isPostCompilingEnabled(),h="undefined"!=typeof i.translateCompile,j=h&&"false"!==i.translateCompile;(g&&!h||j)&&c(o.contents())(d)}else{var k=p.$attr[f];"data-"===k.substr(0,5)&&(k=k.substr(5)),k=k.substr(15),o.attr(k,b)}};(j||l||p.translateDefault)&&h.$watch("interpolateParams",y,!0),h.$on("translateLanguageChanged",y);var B=e.$on("$translateChangeSuccess",y);o.text().length?s(p.translate?p.translate:""):p.translate&&s(p.translate),y(),h.$on("$destroy",B)}}}}function f(a){"use strict";return a.translateNamespace?a.translateNamespace:a.$parent?f(a.$parent):void 0}function g(a,b){"use strict";return{restrict:"A",priority:a.directivePriority(),link:function(c,d,e){var f,g,i={},j=function(){angular.forEach(f,function(b,f){b&&(i[f]=!0,c.translateNamespace&&"."===b.charAt(0)&&(b=c.translateNamespace+b),a(b,g,e.translateInterpolation,void 0,c.translateLanguage).then(function(a){d.attr(f,a)},function(a){d.attr(f,a)}))}),angular.forEach(i,function(a,b){f[b]||(d.removeAttr(b),delete i[b])})};h(c,e.translateAttr,function(a){f=a},j),h(c,e.translateValues,function(a){g=a},j),e.translateValues&&c.$watch(e.translateValues,j,!0),c.$on("translateLanguageChanged",j);var k=b.$on("$translateChangeSuccess",j);j(),c.$on("$destroy",k)}}}function h(a,b,c,d){"use strict";b&&("::"===b.substr(0,2)?b=b.substr(2):a.$watch(b,function(a){c(a),d()},!0),c(a.$eval(b)))}function i(a,b){"use strict";return{compile:function(c){var d=function(b){b.addClass(a.cloakClassName())},e=function(b){b.removeClass(a.cloakClassName())};return d(c),function(c,f,g){var h=e.bind(this,f),i=d.bind(this,f);g.translateCloak&&g.translateCloak.length?(g.$observe("translateCloak",function(b){a(b).then(h,i)}),b.$on("$translateChangeSuccess",function(){a(g.translateCloak).then(h,i)})):a.onReady(h)}}}}function j(){"use strict";return{restrict:"A",scope:!0,compile:function(){return{pre:function(a,b,c){a.translateNamespace=f(a),a.translateNamespace&&"."===c.translateNamespace.charAt(0)?a.translateNamespace+=c.translateNamespace:a.translateNamespace=c.translateNamespace}}}}}function f(a){"use strict";return a.translateNamespace?a.translateNamespace:a.$parent?f(a.$parent):void 0}function k(){"use strict";return{restrict:"A",scope:!0,compile:function(){return function(a,b,c){c.$observe("translateLanguage",function(b){a.translateLanguage=b}),a.$watch("translateLanguage",function(){a.$broadcast("translateLanguageChanged")})}}}}function l(a,b){"use strict";var c=function(c,d,e,f){if(!angular.isObject(d)){var g=this||{__SCOPE_IS_NOT_AVAILABLE:"More info at https://github.com/angular/angular.js/commit/8863b9d04c722b278fa93c5d66ad1e578ad6eb1f"};d=a(d)(g)}return b.instant(c,d,e,f)};return b.statefulFilter()&&(c.$stateful=!0),c}function m(a){"use strict";return a("translations")}return a.$inject=["$translate"],c.$inject=["$STORAGE_KEY","$windowProvider","$translateSanitizationProvider","pascalprechtTranslateOverrider"],d.$inject=["$interpolate","$translateSanitization"],e.$inject=["$translate","$interpolate","$compile","$parse","$rootScope"],g.$inject=["$translate","$rootScope"],i.$inject=["$translate","$rootScope"],l.$inject=["$parse","$translate"],m.$inject=["$cacheFactory"],angular.module("pascalprecht.translate",["ng"]).run(a),a.displayName="runTranslate",angular.module("pascalprecht.translate").provider("$translateSanitization",b),angular.module("pascalprecht.translate").constant("pascalprechtTranslateOverrider",{}).provider("$translate",c),c.displayName="displayName",angular.module("pascalprecht.translate").factory("$translateDefaultInterpolation",d),d.displayName="$translateDefaultInterpolation",angular.module("pascalprecht.translate").constant("$STORAGE_KEY","NG_TRANSLATE_LANG_KEY"),angular.module("pascalprecht.translate").directive("translate",e),e.displayName="translateDirective",angular.module("pascalprecht.translate").directive("translateAttr",g),g.displayName="translateAttrDirective",angular.module("pascalprecht.translate").directive("translateCloak",i),i.displayName="translateCloakDirective",angular.module("pascalprecht.translate").directive("translateNamespace",j),j.displayName="translateNamespaceDirective",angular.module("pascalprecht.translate").directive("translateLanguage",k),k.displayName="translateLanguageDirective",angular.module("pascalprecht.translate").filter("translate",l),l.displayName="translateFilterFactory",angular.module("pascalprecht.translate").factory("$translationCache",m),m.displayName="$translationCache","pascalprecht.translate"});
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/package.json b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/package.json
new file mode 100644
index 0000000000..5c919fc286
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-translate/package.json
@@ -0,0 +1,107 @@
+{
+ "name": "angular-translate",
+ "version": "2.15.1",
+ "description": "A translation module for AngularJS",
+ "main": "dist/angular-translate.js",
+ "repository": {
+ "type": "git",
+ "url": "http://github.com/angular-translate/angular-translate"
+ },
+ "keywords": [
+ "angular-translate",
+ "angular",
+ "AngularJS",
+ "translation"
+ ],
+ "engines": {
+ "node": "*"
+ },
+ "devEngines": {
+ "node": ">=6.9",
+ "npm": ">=3"
+ },
+ "scripts": {
+ "prepublish": "bower install",
+ "check-env": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json",
+ "shipit": "npm run-script -s check-env && bower install && bower update && grunt prepare-release",
+ "lint": "grunt lint",
+ "test": "npm run-script -s check-env && grunt install-test && grunt test",
+ "test-headless": "npm run-script -s check-env && grunt test-headless",
+ "test-scopes": "npm run-script -s check-env && grunt install-test && for f in test_scopes/*; do TEST_SCOPE=\"`basename $f`\" grunt test; done",
+ "clean-test-scopes": "for f in test_scopes/*; do (cd $f; rm -rf bower_components); done",
+ "compile": "npm run-script -s check-env && grunt compile",
+ "build": "npm run-script -s check-env && grunt build",
+ "build-site": "npm run -s build-site-all-languages; npm run -s build-site-plato-report",
+ "build-site-all-languages": "./build_tools/generate_site.sh",
+ "build-site-by-language": "./build_tools/generate_site_by_language.sh",
+ "build-site-plato-report": "rm -rf ./site/plato && plato -d plato -l .jshintrc src/*.js src/**/*.js && mv plato site",
+ "upload-github-release": "node build_tools/upload-github-release.js",
+ "start-demo": "node build_tools/server.js"
+ },
+ "author": {
+ "name": "Pascal Precht"
+ },
+ "contributors": [
+ {
+ "name": "Jan Philipp",
+ "email": "knallisworld@googlemail.com",
+ "url": "https://github.com/knalli"
+ },
+ {
+ "name": "Max Prichinenko"
+ },
+ {
+ "name": "Thorsten S"
+ }
+ ],
+ "license": "MIT",
+ "devDependencies": {
+ "adm-zip": "^0.4.7",
+ "body-parser": "^1.16.0",
+ "bower": "^1.7.9",
+ "errorhandler": "^1.5.0",
+ "express": "^4.14.1",
+ "express-session": "^1.15.0",
+ "fbjs-scripts": "^0.7.1",
+ "grunt": "~1.0.1",
+ "grunt-bower-install-simple": "^1.0.3",
+ "grunt-bump": "^0.8.0",
+ "grunt-cli": "^1.2.0",
+ "grunt-contrib-clean": "^1.0.0",
+ "grunt-contrib-concat": "^1.0.0",
+ "grunt-contrib-copy": "^1.0.0",
+ "grunt-contrib-jshint": "^1.0.0",
+ "grunt-contrib-uglify": "^2.0.0",
+ "grunt-contrib-watch": "^1.0.0",
+ "grunt-conventional-changelog": "^6.1.0",
+ "grunt-file-append": "0.0.6",
+ "grunt-karma": "^2.0.0",
+ "grunt-ng-annotate": "^3.0.0",
+ "grunt-ngdocs": "~0.2.5",
+ "grunt-parallel": "^0.5.1",
+ "grunt-umd": "^2.3.3",
+ "grunt-version": "^1.0.0",
+ "inquirer": "^3.0.1",
+ "jasmine-core": "^2.1.3",
+ "karma": "^1.3.0",
+ "karma-chrome-launcher": "^2.0.0",
+ "karma-coverage": "^1.1.1",
+ "karma-firefox-launcher": "~1.0.0",
+ "karma-jasmine": "^1.0.2",
+ "karma-phantomjs-launcher": "^1.0.0",
+ "load-grunt-tasks": "^3.4.1",
+ "local-web-server": "^1.2.6",
+ "method-override": "^2.3.7",
+ "morgan": "^1.8.0",
+ "multer": "^1.3.0",
+ "phantomjs-prebuilt": "^2.1.4",
+ "plato": "^1.5.0",
+ "publish-release": "^1.3.3",
+ "pug": "^2.0.0-beta11",
+ "serve-favicon": "^2.3.2",
+ "tar.gz": "^1.0.5"
+ },
+ "dependencies": {
+ "angular": ">=1.2.26 <=1.6"
+ }
+}
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/.npmignore b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/.npmignore
new file mode 100644
index 0000000000..007aa77b3d
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/.npmignore
@@ -0,0 +1,38 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules
+jspm_packages
+
+# Optional npm cache directory
+.npm
+
+# Optional REPL history
+.node_repl_history
+.idea
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/LICENSE b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/LICENSE
new file mode 100644
index 0000000000..3a8b61bf73
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2016 it-ailen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/README.md b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/README.md
new file mode 100644
index 0000000000..a65b0abe72
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/README.md
@@ -0,0 +1 @@
+# angular-treeview
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/build/webpack.config.base.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/build/webpack.config.base.js
new file mode 100644
index 0000000000..c4deb062e9
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/build/webpack.config.base.js
@@ -0,0 +1,52 @@
+/**
+ * Created by hyku on 16/9/29.
+ */
+var path = require("path");
+var webpack = require("webpack");
+
+var config = {
+ entry: {
+ tree: "./src/tree.js"
+ },
+ output: {
+ path: path.resolve(__dirname, ".."),
+ filename: "[name].js"
+ },
+ // resolve: {
+ // root: path.resolve(__dirname, "./src")
+ // },
+ module: {
+ loaders: [
+ {
+ test: /\.less$/i,
+ loaders: ["style", "css", "less"]
+ },
+ {
+ test: /\.html$/i,
+ loaders: ["html"]
+ },
+ {
+ test: /\.css$/i,
+ loaders: ["style", "css"]
+ },
+ {
+ test: /\.(jpe?g|png|gif|svg)$/i,
+ loader: "url-loader?limit=10000&name=images/[name].[ext]"
+ },
+ {
+ test: /\.(ttf|eot|woff2?)$/,
+ loader: 'file?name=etc/[name].[ext]'
+ }
+ ]
+ },
+ plugins: [
+ new webpack.optimize.DedupePlugin(),
+ new webpack.ProvidePlugin({
+ $: "jquery",
+ jQuery: "jquery"
+ }),
+ new webpack.optimize.OccurenceOrderPlugin()
+ ]
+};
+
+module.exports = config;
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/images/plus.png b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/images/plus.png
new file mode 100644
index 0000000000..d3e74e624b
Binary files /dev/null and b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/images/plus.png differ
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/index.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/index.js
new file mode 100644
index 0000000000..345b1c0d56
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/index.js
@@ -0,0 +1,7 @@
+/**
+ * Created by hyku on 2016/10/13.
+ */
+
+"use strict";
+require("./tree");
+module.exports = "angular.tree";
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/package.json b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/package.json
new file mode 100644
index 0000000000..3fc653efde
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "angular-treeview",
+ "version": "0.1.5",
+ "description": "Treeview with angular.",
+ "main": "index.js",
+ "devDependencies": {
+ "css-loader": "^0.25.0",
+ "html-loader": "^0.4.4",
+ "less": "^2.7.1",
+ "less-loader": "^2.2.3",
+ "style-loader": "^0.13.1",
+ "url-loader": "^0.5.7",
+ "webpack": "^1.13.2"
+ },
+ "scripts": {
+ "build": "webpack --config build/webpack.config.base.js"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/it-ailen/angular-treeview.git"
+ },
+ "keywords": [
+ "angular",
+ "treeview"
+ ],
+ "author": "Allen Zou",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/it-ailen/angular-treeview/issues"
+ },
+ "homepage": "https://github.com/it-ailen/angular-treeview#readme"
+}
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/tree.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/tree.js
new file mode 100644
index 0000000000..cf34cdb2d5
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/tree.js
@@ -0,0 +1,164 @@
+/**
+ * Created by Allen Zou on 2016/10/13.
+ */
+
+"use strict";
+
+require("./view/tree.less");
+var fileIcon = require("./view/imgs/file.png");
+var folderIcon = require("./view/imgs/folder.png");
+var closedFolderIcon = require("./view/imgs/folder-closed.png");
+var plusIcon = require("./view/imgs/plus.png");
+var removeIcon = require("./view/imgs/remove.png");
+
+var tree = angular.module("angular.tree", []);
+tree
+ .directive("treeNode", function () {
+ return {
+ scope: {
+ item: "=",
+ adapter: "=",
+ icon: "=",
+ folderOpen: "=",
+ folderClose: "=",
+ nodeClick: "=",
+ childrenLoader: "=",
+ addItem: "=",
+ removeItem: "=",
+ editItem: "="
+ },
+ require: [],
+ restrict: "E",
+ // templateUrl: "directive/tree/node.html",
+ template: require("./view/node.html"),
+ link: function($scope, element, attributes, controllers) {
+ $scope.open = false;
+ $scope.add_btn = plusIcon;
+ $scope.remove_btn = removeIcon;
+ function load_children() {
+ if ($scope.childrenLoader) {
+ $scope.childrenLoader($scope.item)
+ .then(function(children) {
+ $scope.subNodes = children;
+ })
+ .catch(function(error) {
+ console.error(error);
+ $scope.subNodes = [];
+ })
+ } else {
+ $scope.subNodes = [];
+ }
+ }
+ $scope.wrap_node_click = function() {
+ if ($scope.item) {
+ var adaptedItem = $scope.adapter($scope.item);
+ if (adaptedItem.type === "branch") {
+ if ($scope.open) {
+ $scope.open = false;
+ $scope.folderClose && $scope.folderClose($scope.item);
+ }
+ else {
+ $scope.open = true;
+ $scope.folderOpen && $scope.folderOpen($scope.item);
+ load_children();
+ }
+ }
+ $scope.nodeClick && $scope.nodeClick($scope.item);
+
+ }
+ return false;
+ };
+ $scope.resolve_icon = function() {
+ var icon = null;
+ var adaptedItem = $scope.adapter($scope.item);
+ if (adaptedItem.type === 'branch') {
+ icon = ($scope.icon && $scope.icon($scope.item, $scope.open))
+ || (!$scope.open && closedFolderIcon)
+ || ($scope.open && folderIcon);
+ }
+ else {
+ icon = ($scope.icon && $scope.icon($scope.item))
+ || fileIcon;
+ }
+ return icon;
+ };
+ $scope.node_class = function() {
+ var classes = ["node"];
+ var adaptedItem = $scope.adapter($scope.item);
+ if (adaptedItem.type === 'branch') {
+ classes.push("branch");
+ if ($scope.open) {
+ classes.push("open");
+ }
+ else {
+ classes.push("closed");
+ }
+ }
+ else {
+ classes.push("leaf");
+ }
+ return classes;
+ };
+ $scope.add_child = function() {
+ if ($scope.addItem) {
+ $scope.addItem($scope.item)
+ .then(function() {
+ load_children();
+ })
+ ;
+ }
+ return false;
+ };
+ $scope.remove_self = function() {
+ if ($scope.removeItem) {
+ $scope.removeItem($scope.item)
+ .then(function() {
+ load_children();
+ })
+ ;
+ }
+ return false;
+ };
+ $scope.edit = function() {
+ console.log("edit:::");
+ console.log($scope.editItem);
+ $scope.editItem && $scope.editItem($scope.item);
+ return false;
+ };
+ }
+ };
+ })
+ .directive("tree", function () {
+ var link = function($scope, element, attributes, controllers) {
+ $scope.itemAdapter = $scope.adapter || function(item) {
+ console.log("in tree .adapter");
+ return item;
+ };
+ $scope.tree_class = function() {
+ var classes = ["tree"];
+ return classes;
+ }
+ };
+ return {
+ scope: {
+ root: "=root",
+ adapter: "=",
+ icon: "=",
+ folderOpen: "=",
+ folderClose: "=",
+ nodeClick: "=",
+ childrenLoader: "=",
+ addItem: "=",
+ removeItem: "=",
+ editItem: "="
+ },
+ require: [],
+ restrict: "E",
+ // templateUrl: "directive/tree/tree.html",
+ template: require("./view/tree.html"),
+ link: link
+ }
+ })
+;
+
+module.exports = tree;
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/imgs/file.png b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/imgs/file.png
new file mode 100644
index 0000000000..ffd22db2cc
Binary files /dev/null and b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/imgs/file.png differ
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/imgs/folder-closed.png b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/imgs/folder-closed.png
new file mode 100644
index 0000000000..9c8489c12d
Binary files /dev/null and b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/imgs/folder-closed.png differ
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/imgs/folder.png b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/imgs/folder.png
new file mode 100644
index 0000000000..fdad546d71
Binary files /dev/null and b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/imgs/folder.png differ
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/imgs/plus.png b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/imgs/plus.png
new file mode 100644
index 0000000000..0aac69bf1a
Binary files /dev/null and b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/imgs/plus.png differ
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/imgs/remove.png b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/imgs/remove.png
new file mode 100644
index 0000000000..9e081451c0
Binary files /dev/null and b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/imgs/remove.png differ
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/node.html b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/node.html
new file mode 100644
index 0000000000..101147d649
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/node.html
@@ -0,0 +1,24 @@
+
+
+
+
{{ adapter(item).text }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/tree.html b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/tree.html
new file mode 100644
index 0000000000..7717002dc7
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/tree.html
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/tree.less b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/tree.less
new file mode 100644
index 0000000000..c6373c8817
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/src/view/tree.less
@@ -0,0 +1,33 @@
+.tree {
+ @node-height: 16px;
+ overflow: auto;
+ .node {
+ width: 100%;
+ .directory-level {
+ position: relative;
+ padding-right: 4px;
+ white-space: nowrap;
+ font-size: @node-height;
+ line-height: @node-height;
+ >.icon {
+ height: @node-height;
+ }
+ .operation {
+ display: inline;
+ margin-left: 20px;
+ visibility: hidden;
+ img {
+ height: @node-height;
+ }
+ }
+ &:hover {
+ .operation {
+ visibility: visible;
+ }
+ }
+ }
+ .sub-node {
+ padding-left: 14px;
+ }
+ }
+}
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/tree.js b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/tree.js
new file mode 100644
index 0000000000..a121bb0e44
--- /dev/null
+++ b/themes/src/main/resources/theme/keycloak/common/resources/node_modules/angular-treeview/tree.js
@@ -0,0 +1,604 @@
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+
+
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /**
+ * Created by Allen Zou on 2016/10/13.
+ */
+
+ "use strict";
+
+ __webpack_require__(6);
+ var fileIcon = __webpack_require__(7);
+ var folderIcon = __webpack_require__(9);
+ var closedFolderIcon = __webpack_require__(8);
+ var plusIcon = __webpack_require__(10);
+ var removeIcon = __webpack_require__(11);
+
+ var tree = angular.module("angular.tree", []);
+ tree
+ .directive("treeNode", function () {
+ return {
+ scope: {
+ item: "=",
+ adapter: "=",
+ icon: "=",
+ folderOpen: "=",
+ folderClose: "=",
+ nodeClick: "=",
+ childrenLoader: "=",
+ addItem: "=",
+ removeItem: "=",
+ editItem: "="
+ },
+ require: [],
+ restrict: "E",
+ // templateUrl: "directive/tree/node.html",
+ template: __webpack_require__(3),
+ link: function($scope, element, attributes, controllers) {
+ $scope.open = false;
+ $scope.add_btn = plusIcon;
+ $scope.remove_btn = removeIcon;
+ function load_children() {
+ if ($scope.childrenLoader) {
+ $scope.childrenLoader($scope.item)
+ .then(function(children) {
+ $scope.subNodes = children;
+ })
+ .catch(function(error) {
+ console.error(error);
+ $scope.subNodes = [];
+ })
+ } else {
+ $scope.subNodes = [];
+ }
+ }
+ $scope.wrap_node_click = function() {
+ if ($scope.item) {
+ var adaptedItem = $scope.adapter($scope.item);
+ if (adaptedItem.type === "branch") {
+ if ($scope.open) {
+ $scope.open = false;
+ $scope.folderClose && $scope.folderClose($scope.item);
+ }
+ else {
+ $scope.open = true;
+ $scope.folderOpen && $scope.folderOpen($scope.item);
+ load_children();
+ }
+ }
+ $scope.nodeClick && $scope.nodeClick($scope.item);
+
+ }
+ return false;
+ };
+ $scope.resolve_icon = function() {
+ var icon = null;
+ var adaptedItem = $scope.adapter($scope.item);
+ if (adaptedItem.type === 'branch') {
+ icon = ($scope.icon && $scope.icon($scope.item, $scope.open))
+ || (!$scope.open && closedFolderIcon)
+ || ($scope.open && folderIcon);
+ }
+ else {
+ icon = ($scope.icon && $scope.icon($scope.item))
+ || fileIcon;
+ }
+ return icon;
+ };
+ $scope.node_class = function() {
+ var classes = ["node"];
+ var adaptedItem = $scope.adapter($scope.item);
+ if (adaptedItem.type === 'branch') {
+ classes.push("branch");
+ if ($scope.open) {
+ classes.push("open");
+ }
+ else {
+ classes.push("closed");
+ }
+ }
+ else {
+ classes.push("leaf");
+ }
+ return classes;
+ };
+ $scope.add_child = function() {
+ if ($scope.addItem) {
+ $scope.addItem($scope.item)
+ .then(function() {
+ load_children();
+ })
+ ;
+ }
+ return false;
+ };
+ $scope.remove_self = function() {
+ if ($scope.removeItem) {
+ $scope.removeItem($scope.item)
+ .then(function() {
+ load_children();
+ })
+ ;
+ }
+ return false;
+ };
+ $scope.edit = function() {
+ console.log("edit:::");
+ console.log($scope.editItem);
+ $scope.editItem && $scope.editItem($scope.item);
+ return false;
+ };
+ }
+ };
+ })
+ .directive("tree", function () {
+ var link = function($scope, element, attributes, controllers) {
+ $scope.itemAdapter = $scope.adapter || function(item) {
+ console.log("in tree .adapter");
+ return item;
+ };
+ $scope.tree_class = function() {
+ var classes = ["tree"];
+ return classes;
+ }
+ };
+ return {
+ scope: {
+ root: "=root",
+ adapter: "=",
+ icon: "=",
+ folderOpen: "=",
+ folderClose: "=",
+ nodeClick: "=",
+ childrenLoader: "=",
+ addItem: "=",
+ removeItem: "=",
+ editItem: "="
+ },
+ require: [],
+ restrict: "E",
+ // templateUrl: "directive/tree/tree.html",
+ template: __webpack_require__(4),
+ link: link
+ }
+ })
+ ;
+
+ module.exports = tree;
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports, __webpack_require__) {
+
+ exports = module.exports = __webpack_require__(2)();
+ // imports
+
+
+ // module
+ exports.push([module.id, ".tree {\n overflow: auto;\n}\n.tree .node {\n width: 100%;\n}\n.tree .node .directory-level {\n position: relative;\n padding-right: 4px;\n white-space: nowrap;\n font-size: 16px;\n line-height: 16px;\n}\n.tree .node .directory-level > .icon {\n height: 16px;\n}\n.tree .node .directory-level .operation {\n display: inline;\n margin-left: 20px;\n visibility: hidden;\n}\n.tree .node .directory-level .operation img {\n height: 16px;\n}\n.tree .node .directory-level:hover .operation {\n visibility: visible;\n}\n.tree .node .sub-node {\n padding-left: 14px;\n}\n", ""]);
+
+ // exports
+
+
+/***/ },
+/* 2 */
+/***/ function(module, exports) {
+
+ /*
+ MIT License http://www.opensource.org/licenses/mit-license.php
+ Author Tobias Koppers @sokra
+ */
+ // css base code, injected by the css-loader
+ module.exports = function() {
+ var list = [];
+
+ // return the list of modules as css string
+ list.toString = function toString() {
+ var result = [];
+ for(var i = 0; i < this.length; i++) {
+ var item = this[i];
+ if(item[2]) {
+ result.push("@media " + item[2] + "{" + item[1] + "}");
+ } else {
+ result.push(item[1]);
+ }
+ }
+ return result.join("");
+ };
+
+ // import a list of modules into the list
+ list.i = function(modules, mediaQuery) {
+ if(typeof modules === "string")
+ modules = [[null, modules, ""]];
+ var alreadyImportedModules = {};
+ for(var i = 0; i < this.length; i++) {
+ var id = this[i][0];
+ if(typeof id === "number")
+ alreadyImportedModules[id] = true;
+ }
+ for(i = 0; i < modules.length; i++) {
+ var item = modules[i];
+ // skip already imported module
+ // this implementation is not 100% perfect for weird media query combinations
+ // when a module is imported multiple times with different media queries.
+ // I hope this will never occur (Hey this way we have smaller bundles)
+ if(typeof item[0] !== "number" || !alreadyImportedModules[item[0]]) {
+ if(mediaQuery && !item[2]) {
+ item[2] = mediaQuery;
+ } else if(mediaQuery) {
+ item[2] = "(" + item[2] + ") and (" + mediaQuery + ")";
+ }
+ list.push(item);
+ }
+ }
+ };
+ return list;
+ };
+
+
+/***/ },
+/* 3 */
+/***/ function(module, exports) {
+
+ module.exports = "\n
\n
\n
{{ adapter(item).text }} \n
\n
\n
\n \n \n
\n
";
+
+/***/ },
+/* 4 */
+/***/ function(module, exports) {
+
+ module.exports = "\n \n \n
";
+
+/***/ },
+/* 5 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /*
+ MIT License http://www.opensource.org/licenses/mit-license.php
+ Author Tobias Koppers @sokra
+ */
+ var stylesInDom = {},
+ memoize = function(fn) {
+ var memo;
+ return function () {
+ if (typeof memo === "undefined") memo = fn.apply(this, arguments);
+ return memo;
+ };
+ },
+ isOldIE = memoize(function() {
+ return /msie [6-9]\b/.test(window.navigator.userAgent.toLowerCase());
+ }),
+ getHeadElement = memoize(function () {
+ return document.head || document.getElementsByTagName("head")[0];
+ }),
+ singletonElement = null,
+ singletonCounter = 0,
+ styleElementsInsertedAtTop = [];
+
+ module.exports = function(list, options) {
+ if(false) {
+ if(typeof document !== "object") throw new Error("The style-loader cannot be used in a non-browser environment");
+ }
+
+ options = options || {};
+ // Force single-tag solution on IE6-9, which has a hard limit on the # of
+
+
+
+
+
+
+
+
+
+
+
+ The Basics
+
+
+ <select>
+
+
+ Alaska
+ Hawaii
+
+
+ California
+ Nevada
+ Oregon
+ Washington
+
+
+ Arizona
+ Colorado
+ Idaho
+ Montana
+ Nebraska
+ New Mexico
+ North Dakota
+ Utah
+ Wyoming
+
+
+ Alabama
+ Arkansas
+ Illinois
+ Iowa
+ Kansas
+ Kentucky
+ Louisiana
+ Minnesota
+ Mississippi
+ Missouri
+ Oklahoma
+ South Dakota
+ Texas
+ Tennessee
+ Wisconsin
+
+
+ Connecticut
+ Delaware
+ Florida
+ Georgia
+ Indiana
+ Maine
+ Maryland
+ Massachusetts
+ Michigan
+ New Hampshire
+ New Jersey
+ New York
+ North Carolina
+ Ohio
+ Pennsylvania
+ Rhode Island
+ South Carolina
+ Vermont
+ Virginia
+ West Virginia
+
+
+
+
+
Value
+
{{ basicsValue }}
+
+
+
+
+
+ Multi-Value
+
+
+ <select multiple>
+
+
+
+
Value
+
{{ multiValue }}
+
+
+
+
+ Pre-defined value
+
+
+
+
Config
+
{
+ multiple: true,
+ query: function (query) {
+ query.callback({ results: states });
+ },
+ initSelection: function(element, callback) {
+ var val = $(element).select2('val'),
+ results = [];
+ for (var i=0; i<val.length; i++) {
+ results.push(findState(val[i]));
+ }
+ callback(results);
+ }
+ }
+
Value
+
{{ multi2Value }}
+
+
+
+
+
+ Placeholders
+
+
+ Using data-attribute
+
+
+
+
Value
+
{{ placeholdersValue }}
+
+
+
+
+ Multiple, using config
+
+
+
+
Config
+
{{ placeholders }}
+
Value
+
{{ placeholdersMultiValue }}
+
+
+
+
+
+ Array Data
+
+
+ Simple
+
+
+
+
Config
+
{{ array }}
+
Value
+
{{ arrayValue }}
+
+
+
+
+ Asynchronous
+
+
+
+
Config
+
{
+ query: function (query) {
+ query.callback({ results: states });
+ },
+ initSelection: function(element, callback) {
+ var val = $(element).select2('val');
+ return callback(findState(val));
+ }
+ }
+
Value
+
{{ arrayAsyncValue }}
+
+
+
+
+
+
+