diff --git a/app/includes/footer.tpl b/app/includes/footer.tpl index 5b40c09a80..42adc9cf0d 100644 --- a/app/includes/footer.tpl +++ b/app/includes/footer.tpl @@ -41,6 +41,7 @@
+ETH Block#: {{ethBlockNumber}} · ETC Block#: {{etcBlockNumber}}
diff --git a/app/scripts/controllers/footerCtrl.js b/app/scripts/controllers/footerCtrl.js index 694cea9d50..f2ff38f5f7 100644 --- a/app/scripts/controllers/footerCtrl.js +++ b/app/scripts/controllers/footerCtrl.js @@ -10,6 +10,6 @@ var footerCtrl = function($scope) { ajaxReq.getCurrentBlock(true, function(data){$scope.etcBlockNumber = data.data;}); } $scope.setBlockNumbers(); - setInterval($scope.setBlockNumbers,3000); + //setInterval($scope.setBlockNumbers,3000); }; module.exports = footerCtrl; diff --git a/app/scripts/ethFuncs.js b/app/scripts/ethFuncs.js index c0a05d6efa..bec7b06bb7 100644 --- a/app/scripts/ethFuncs.js +++ b/app/scripts/ethFuncs.js @@ -63,17 +63,19 @@ ethFuncs.getDataObj = function(to, func, arrVals) { data: func + val }; } -ethFuncs.ecSignEIP155 = function(msgHash, privateKey){ +ethFuncs.ecSignEIP155 = function(msgHash, privateKey, old){ var sig = ethUtil.secp256k1.sign(msgHash, privateKey); var ret = {}; ret.r = sig.signature.slice(0, 32); ret.s = sig.signature.slice(32, 64); - ret.v = sig.recovery + 37; + ret.v = old ? sig.recovery + 27 : sig.recovery + 37; return ret; } -ethFuncs.hashEIP155 = function(tx){ +ethFuncs.hashEIP155 = function(tx, old){ tx.raw[6] = 18; - return ethUtil.rlphash(tx.raw) + var toHash = tx.raw; + if(old) toHash = toHash.splice(0,6); + return ethUtil.rlphash(toHash); } ethFuncs.estimateGas = function(dataObj, isClassic, callback) { var gasLimit = 2000000; diff --git a/app/scripts/globalFuncs.js b/app/scripts/globalFuncs.js index c0a92300e5..f7bbf84a26 100644 --- a/app/scripts/globalFuncs.js +++ b/app/scripts/globalFuncs.js @@ -125,7 +125,8 @@ globalFuncs.isNumeric = function(n) { return !isNaN(parseFloat(n)) && isFinite(n); } globalFuncs.urlGet = function(name) { - if (name = (new RegExp('[?&]' + encodeURIComponent(name) + '=([^&]*)')).exec(location.search)) return this.stripTags(decodeURIComponent(name[1])); + name = name.toLowerCase(); + if (name = (new RegExp('[?&]' + encodeURIComponent(name) + '=([^&]*)')).exec(location.search.toLowerCase())) return this.stripTags(decodeURIComponent(name[1])); } globalFuncs.stripTags = function(str) { var SCRIPT_REGEX = /"); -} -globalFuncs.getBlob = function(mime, str) { - var str = (typeof str === 'object') ? JSON.stringify(str) : str; - if (str == null) return ''; - var blob = new Blob([str], { - type: mime - }); - return window.URL.createObjectURL(blob); -} -globalFuncs.getSuccessText = function(str) { - return '' + str + '
' -} -globalFuncs.getDangerText = function(str) { - return '' + str + '
' -} -globalFuncs.errorMsgs = [ - "Please enter valid amount.", - "Your password must be at least 9 characters. Please ensure it is a strong password. ", - "Sorry! We don\'t recognize this type of wallet file. ", - "This is not a valid wallet file. ", - "This unit doesn\'t exists, please use the one of the following units ", - "Invalid address. ", - "Invalid password. ", - "Invalid amount. ", - "Invalid gas limit. ", - "Invalid data value. ", - "Invalid gas amount. ", // 10 - "Invalid nonce. ", - "Invalid signed transaction. ", - "A wallet with this nickname already exists. ", - "Wallet not found. ", - "Whoops. It doesnt look like a proposal with this ID exists yet or there is an error reading this proposal. ", // 15 - "A wallet with this address already exists in storage. Please check your wallets page. ", - "You need to have at least 0.01 ETH in your account to cover the cost of gas. Please add some ETH and try again. ", - "All gas would be used on this transaction. This means you have already voted on this proposal or the debate period has ended.", - "Invalid symbol", - "Not a valid ERC-20 token", - "Could not estimate gas. There are not enough funds in the account, or the receiving contract address would throw an error. Feel free to manually set the gas and proceed. The error message upon sending may be more informative." - ]; -globalFuncs.successMsgs = [ - "Valid address", - "Wallet successfully decrypted", - "Transaction submitted. TX ID: ", - "Your wallet was successfully added: ", - "You have successfully voted. Thank you for being an active participant in The DAO.", - "File Selected: "]; -globalFuncs.gethErrors = { - "Invalid sender": "GETH_InvalidSender", - "Nonce too low": "GETH_Nonce", - "Gas price too low for acceptance": "GETH_Cheap", - "Insufficient balance": "GETH_Balance", - "Account does not exist or account balance too low": "GETH_NonExistentAccount", - "Insufficient funds for gas * price + value": "GETH_InsufficientFunds", - "Intrinsic gas too low": "GETH_IntrinsicGas", - "Exceeds block gas limit": "GETH_GasLimit", - "Negative value": "GETH_NegativeValue"}; -globalFuncs.gethErrorMsgs = {}; -globalFuncs.getGethMsg = function(str) { - if (str in this.gethErrors) { - var key = this.gethErrors[str]; - if (key in this.gethErrorMsgs) { - return this.gethErrorMsgs[key]; - } - } - return str; -} -globalFuncs.parityErrors = { - "Transaction with the same hash was already imported\\.": "PARITY_AlreadyImported", - "Transaction nonce is too low\\. Try incrementing the nonce\\.": "PARITY_Old", - "Transaction fee is too low\\. There is another transaction with same nonce in the queue\\. Try increasing the fee or incrementing the nonce\\.": "PARITY_TooCheapToReplace", - "There are too many transactions in the queue\\. Your transaction was dropped due to limit\\. Try increasing the fee\\.": "PARITY_LimitReached", - "Transaction fee is too low\\. It does not satisfy your node's minimal fee \\(minimal: (\\d+), got: (\\d+)\\)\\. Try increasing the fee\\.": "PARITY_InsufficientGasPrice", - "Insufficient funds\\. Account you try to send transaction from does not have enough funds\\. Required (\\d+) and got: (\\d+)\\.": "PARITY_InsufficientBalance", - "Transaction cost exceeds current gas limit\\. Limit: (\\d+), got: (\\d+)\\. Try decreasing supplied gas\\.": "PARITY_GasLimitExceeded", - "Supplied gas is beyond limit\\.": "PARITY_InvalidGasLimit"}; -globalFuncs.parityErrorMsgs = {}; -globalFuncs.getParityMsg = function(str) { - for (var reg in this.parityErrors) { - var args = str.match("^"+reg+"$"); - if (args) { - var key = this.parityErrors[reg]; - if (key in this.parityErrorMsgs) { - args[0] = this.parityErrorMsgs[key]; - return format.apply(this,args); - } - } - } - return str; -} -globalFuncs.getEthNodeName = function() { -// return "geth"; - return "parity"; -} -globalFuncs.getEthNodeMsg = function(str) { - var ethNode = this.getEthNodeName(); - if (ethNode == "geth") - return this.getGethMsg(str); - else - return this.getParityMsg(str); -} -globalFuncs.scrypt = { - n: 1024 -}; -globalFuncs.postDelay = 300; -globalFuncs.kdf = "scrypt"; -globalFuncs.defaultTxGasLimit = 21000; -globalFuncs.digixClaimTxGasLimit = 150000; -globalFuncs.donateAddress = "0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8"; -globalFuncs.isNumeric = function(n) { - return !isNaN(parseFloat(n)) && isFinite(n); -} -globalFuncs.urlGet = function(name) { - if (name = (new RegExp('[?&]' + encodeURIComponent(name) + '=([^&]*)')).exec(location.search)) return this.stripTags(decodeURIComponent(name[1])); -} -globalFuncs.stripTags = function(str) { - var SCRIPT_REGEX = / -Directive | -How | -Source | -Rendered | -
ng-bind-html | -Automatically uses $sanitize | -<div ng-bind-html="snippet"> |
- - |
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"> |
- - |
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')).getInnerHtml()). - toBe("an html\n" + - "click here\n" + - "snippet
"); - }); - - it('should escape snippet without any filter', function() { - expect(element(by.css('#bind-default div')).getInnerHtml()). - 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')).getInnerHtml()). - toBe('new text'); - expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe( - 'new text'); - expect(element(by.css('#bind-default div')).getInnerHtml()).toBe( - "new <b onclick=\"alert(1)\">text</b>"); - }); -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;
- * }
- *
- * Filter | -Source | -Rendered | -
---|---|---|
linky filter | -
- <div ng-bind-html="snippet | linky">- |
- - - | -
linky target | -
- <div ng-bind-html="snippetWithSingleURL | linky:'_blank'">- |
- - - | -
linky custom attributes | -
- <div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}">- |
- - - | -
no filter | -<div ng-bind="snippet"> |
- - |
- * // 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])); + }); + var userInfo = ethFuncs.getDataObj($scope.eBTokenContract, $scope.hexCodes.balanceOf, [ethFuncs.getNakedAddress($scope.wallet.getAddressString())]); + ajaxReq.getEthCall(userInfo, false, function (data) { + if (!data.error) { + $scope.token.eBTokenBalance = new BigNumber(data.data).div(etherUnits.getValueOfUnit('milli') * 10).toString(); + $scope.token.eBEthBalance = new BigNumber($scope.token.eBTokenBalance).div(100).toString(); + $scope.token.eBBalanceBN = new BigNumber(data.data).toString(); } - // 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); - } + }); + var userInfo = ethFuncs.getDataObj($scope.daoCContract, $scope.hexCodes.balanceOf, [ethFuncs.getNakedAddress($scope.wallet.getAddressString())]); + ajaxReq.getEthCall(userInfo, true, function (data) { + if (!data.error) { + $scope.token.DCbalance = new BigNumber(data.data).div(etherUnits.getValueOfUnit('milli') * 10).toString(); } - } - 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; + }); + var userInfo = ethFuncs.getDataObj($scope.daoWithdrawContract, $scope.hexCodes.numETChex, [ethFuncs.getNakedAddress($scope.wallet.getAddressString())]); + ajaxReq.getEthCall(userInfo, true, function (data) { + if (!data.error) { + $scope.token.DCbalanceEth = new BigNumber(data.data).div(etherUnits.getValueOfUnit('milli') * 1000).toString(); } - determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText, uses).then(deferred.resolve, deferred.reject); - }; - promiseResolved.displayName = 'promiseResolved'; - - promiseToWaitFor['finally'](promiseResolved); - } - 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); + }); + ajaxReq.getBalance($scope.wallet.getAddressString(), true, function (data) { + if (data.error) { + $scope.etcBalance = data.msg; + } else { + $scope.etcBalance = etherUnits.toEther(data.data.balance, 'wei'); + } + }); }; - eachInterpolator.displayName = 'eachInterpolatorLocaleSetter'; - - // inform all others too! - angular.forEach(interpolatorHashMap, eachInterpolator); - $rootScope.$emit('$translateChangeEnd', {language : key}); - }; - - /** - * @name loadAsync - * @private - * - * @description - * Kicks of 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) + $scope.$watch('curTab', function () { + $scope.tx.data = ''; + $scope.showRaw = $scope.showProposal = false; }); - - 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)); + $scope.$watch('[tx,curTab]', function () { + $scope.showRaw = false; + $scope.sendTxStatus = ""; + $scope.withdrawTxStatus = ""; + }, true); + $scope.generateAndSendWithdrawTx = function (tokenContract, withdrawContract, balanceBN, modal) { + $scope.tx.to = tokenContract; + $scope.tx.data = $scope.hexCodes.approveWithdraw + ethFuncs.padLeft(ethFuncs.getNakedAddress(withdrawContract), 64) + ethFuncs.padLeft(new BigNumber(balanceBN).toString(16), 64); + $scope.tx.value = 0; + uiFuncs.generateTx(uiFuncs.getTxData($scope), false, function (rawTx) { + uiFuncs.sendTx(rawTx.signedTx, false, function (resp) { + if (resp.isError) { + $scope.sendTxStatus = $sce.trustAsHtml(globalFuncs.getDangerText(resp.error)); + } else { + $scope.sendTxStatus = $sce.trustAsHtml("Please Wait"); + var approveTx = resp.data; + setTimeout(function () { + $scope.tx.to = withdrawContract; + $scope.tx.data = $scope.hexCodes.daoWithdraw; + uiFuncs.generateTx(uiFuncs.getTxData($scope), false, function (rawTx) { + uiFuncs.sendTx(rawTx.signedTx, false, function (resp) { + if (resp.isError) { + $scope.withdrawTxStatus = $sce.trustAsHtml(globalFuncs.getDangerText(data.error)); + } else { + $scope.sendTxStatus = $sce.trustAsHtml("Approval Transaction: " + globalFuncs.getSuccessText(globalFuncs.successMsgs[2] + "" + approveTx + "")); + $scope.withdrawTxStatus = $sce.trustAsHtml("Withdrawal Transaction: " + globalFuncs.getSuccessText(globalFuncs.successMsgs[2] + "" + resp.data + "")); + $scope.setBalance(); + } + }); + }); + }, 3000); + } }); - } else { - angular.extend(translationTable, flatObject(data)); - } - pendingLoader = false; - deferred.resolve({ - key : key, - table : translationTable }); - $rootScope.$emit('$translateLoadingEnd', {language : key}); + $scope[modal].close(); }; - onLoaderSuccess.displayName = 'onLoaderSuccess'; - - var onLoaderError = function (key) { - $rootScope.$emit('$translateLoadingError', {language : key}); - deferred.reject(key); - $rootScope.$emit('$translateLoadingEnd', {language : key}); + $scope.generateAndWithdrawDAOC = function () { + if (!Validator.isPositiveNumber($scope.daoC.donation)) { + $scope.withdrawETCTxStatus = $sce.trustAsHtml(globalFuncs.getDangerText(globalFuncs.errorMsgs[0])); + return; + } + $scope.tx.to = $scope.daoWithdrawContract; + $scope.tx.data = $scope.hexCodes.withdrawDAOC + ethFuncs.padLeft(ethFuncs.getNakedAddress($scope.daoC.to), 64) + ethFuncs.padLeft(new BigNumber($scope.daoC.donation).toString(16), 64); + $scope.tx.value = 0; + uiFuncs.generateTx(uiFuncs.getTxData($scope), true, function (rawTx) { + uiFuncs.sendTx(rawTx.signedTx, true, function (resp) { + if (resp.isError) { + $scope.withdrawETCTxStatus = $sce.trustAsHtml(globalFuncs.getDangerText(resp.error)); + } else { + $scope.withdrawETCTxStatus = $sce.trustAsHtml("Withdrawal Transaction: " + globalFuncs.getSuccessText(globalFuncs.successMsgs[2] + " ETC TX via GasTracker.io")); + $scope.setBalance(); + } + }); + }); + $scope.withdrawModalETC.close(); }; - onLoaderError.displayName = 'onLoaderError'; - - $injector.get($loaderFactory)(loaderOptions) - .then(onLoaderSuccess, onLoaderError); - - return deferred.promise; }; + module.exports = theDaoCtrl; + }, {}], 16: [function (require, module, exports) { + 'use strict'; - 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; + var viewCtrl = function viewCtrl($scope, globalService) { + $scope.globalService = globalService; }; - - /** - * @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 - * @returns {Q.promise} - */ - var getFallbackTranslation = function (langKey, translationId, interpolateParams, Interpolator) { - var deferred = $q.defer(); - - var onResolve = function (translationTable) { - if (Object.prototype.hasOwnProperty.call(translationTable, translationId)) { - Interpolator.setLocale(langKey); - var translation = translationTable[translationId]; - if (translation.substr(0, 2) === '@:') { - getFallbackTranslation(langKey, translation.substr(2), interpolateParams, Interpolator) - .then(deferred.resolve, deferred.reject); + module.exports = viewCtrl; + }, {}], 17: [function (require, module, exports) { + 'use strict'; + + var viewWalletCtrl = function viewWalletCtrl($scope, walletService) { + $scope.usdBalance = "loading"; + $scope.eurBalance = "loading"; + $scope.btcBalance = "loading"; + $scope.etherBalance = "loading"; + + $scope.tokenVisibility = "hidden"; + walletService.wallet = null; + walletService.password = ''; + $scope.$watch(function () { + if (walletService.wallet == null) return null; + return walletService.wallet.getAddressString(); + }, function () { + if (walletService.wallet == null) return; + $scope.wallet = walletService.wallet; + $scope.wd = true; + $scope.showEnc = walletService.password != ''; + if (walletService.wallet.type == "default") $scope.blob = globalFuncs.getBlob("text/json;charset=UTF-8", $scope.wallet.toJSON()); + if (walletService.password != '') { + $scope.blobEnc = globalFuncs.getBlob("text/json;charset=UTF-8", $scope.wallet.toV3(walletService.password, { + kdf: globalFuncs.kdf, + n: globalFuncs.scrypt.n + })); + $scope.encFileName = $scope.wallet.getV3Filename(); + } + ajaxReq.getBalance($scope.wallet.getAddressString(), false, function (data) { + if (data.error) { + $scope.etherBalance = data.msg; } else { - var interpolatedValue = Interpolator.interpolate(translationTable[translationId], interpolateParams, 'service'); - interpolatedValue = applyPostProcessing(translationId, translationTable[translationId], interpolatedValue, interpolateParams, langKey); - - deferred.resolve(interpolatedValue); - + $scope.etherBalance = etherUnits.toEther(data.data.balance, 'wei'); + ajaxReq.getETHvalue(function (data) { + $scope.usdBalance = etherUnits.toFiat($scope.etherBalance, 'ether', data.usd); + $scope.eurBalance = etherUnits.toFiat($scope.etherBalance, 'ether', data.eur); + $scope.btcBalance = etherUnits.toFiat($scope.etherBalance, 'ether', data.btc); + }); } - Interpolator.setLocale($uses); - } else { - deferred.reject(); + }); + $scope.setTokens(); + }); + + // Populate token list + from local storage + $scope.setTokens = function () { + $scope.tokenObjs = []; + $scope.tokens = Token.popTokens; + for (var i = 0; i < $scope.tokens.length; i++) { + $scope.tokenObjs.push(new Token($scope.tokens[i].address, $scope.wallet.getAddressString(), $scope.tokens[i].symbol, $scope.tokens[i].decimal, $scope.tokens[i].type)); + $scope.tokenObjs[$scope.tokenObjs.length - 1].setBalance(); + } + var storedTokens = localStorage.getItem("localTokens") != null ? JSON.parse(localStorage.getItem("localTokens")) : []; + for (var i = 0; i < storedTokens.length; i++) { + $scope.tokenObjs.push(new Token(storedTokens[i].contractAddress, $scope.wallet.getAddressString(), globalFuncs.stripTags(storedTokens[i].symbol), storedTokens[i].decimal, storedTokens[i].type)); + $scope.tokenObjs[$scope.tokenObjs.length - 1].setBalance(); } }; - onResolve.displayName = 'fallbackTranslationResolver'; - getTranslationTable(langKey).then(onResolve, deferred.reject); - - return deferred.promise; - }; + // Remove tokens from localstorage when they click the 'X' + $scope.removeTokenFromLocal = function (tokenSymbol) { + var storedTokens = localStorage.getItem("localTokens") != null ? JSON.parse(localStorage.getItem("localTokens")) : []; - /** - * @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]; + // remove from localstorage so it doesn't show up on refresh + for (var i = 0; i < storedTokens.length; i++) { + if (storedTokens[i].symbol === tokenSymbol) { + storedTokens.splice(i, 1); + break; + } + }localStorage.setItem("localTokens", JSON.stringify(storedTokens)); - if (translationTable && Object.prototype.hasOwnProperty.call(translationTable, translationId)) { - Interpolator.setLocale(langKey); - result = Interpolator.interpolate(translationTable[translationId], interpolateParams, 'filter', sanitizeStrategy); - 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); + // remove from tokenObj so it removes from display + for (var i = 0; i < $scope.tokenObjs.length; i++) { + if ($scope.tokenObjs[i].symbol === tokenSymbol) { + $scope.tokenObjs.splice(i, 1); + break; } - } else if (result.substr(0, 2) === '@:') { - return getFallbackTranslationInstant(langKey, result.substr(2), interpolateParams, Interpolator, sanitizeStrategy); } - Interpolator.setLocale($uses); - } + }; - return result; + $scope.printQRCode = function () { + globalFuncs.printPaperWallets(JSON.stringify([{ + address: $scope.wallet.getAddressString(), + private: $scope.wallet.getPrivateKeyString() + }])); + }; }; - - - /** - * @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; - } + module.exports = viewWalletCtrl; + }, {}], 18: [function (require, module, exports) { + 'use strict'; + + var walletGenCtrl = function walletGenCtrl($scope) { + $scope.password = ""; + $scope.wallet = null; + $scope.showWallet = false; + $scope.blob = $scope.blobEnc = ""; + $scope.isDone = true; + $scope.showPass = true; + $scope.genNewWallet = function () { + if (!$scope.isStrongPass()) { + alert(globalFuncs.errorMsgs[1]); + } else if ($scope.isDone) { + $scope.isDone = false; + $scope.wallet = Wallet.generate(false); + $scope.showWallet = true; + $scope.blob = globalFuncs.getBlob("text/json;charset=UTF-8", $scope.wallet.toJSON()); + $scope.blobEnc = globalFuncs.getBlob("text/json;charset=UTF-8", $scope.wallet.toV3($scope.password, { + kdf: globalFuncs.kdf, + n: globalFuncs.scrypt.n + })); + $scope.encFileName = $scope.wallet.getV3Filename(); + if (parent != null) parent.postMessage(JSON.stringify({ address: $scope.wallet.getAddressString(), checksumAddress: $scope.wallet.getChecksumAddressString() }), "*"); + $scope.isDone = true; + } + }; + $scope.printQRCode = function () { + globalFuncs.printPaperWallets(JSON.stringify([{ + address: $scope.wallet.getAddressString(), + private: $scope.wallet.getPrivateKeyString() + }])); + }; + $scope.isStrongPass = function () { + return globalFuncs.isStrongPass($scope.password); + }; }; - - /** - * @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 - * @returns {Q.promise} Promise that will resolve to the translation. - */ - var resolveForFallbackLanguage = function (fallbackLanguageIndex, translationId, interpolateParams, Interpolator, defaultTranslationText) { - var deferred = $q.defer(); - - if (fallbackLanguageIndex < $fallbackLanguage.length) { - var langKey = $fallbackLanguage[fallbackLanguageIndex]; - getFallbackTranslation(langKey, translationId, interpolateParams, Interpolator).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).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)); - } + module.exports = walletGenCtrl; + }, {}], 19: [function (require, module, exports) { + 'use strict'; + + var marked = require('marked'); + var myRenderer = new marked.Renderer(); + myRenderer.paragraph = function (text) { + return text + '\n'; + }; + myRenderer.link = function (href, title, text) { + if (this.options.sanitize) { + try { + var prot = decodeURIComponent(unescape(href)).replace(/[^\w:]/g, '').toLowerCase(); + } catch (e) { + return ''; } + if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0) { + return ''; + } + } + var out = '' + text + ''; + return out; }; - - /** - * @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); + marked.setOptions({ + renderer: myRenderer + }); + module.exports = marked; + }, { "marked": 172 }], 20: [function (require, module, exports) { + 'use strict'; + + var cxFuncs = function cxFuncs() {}; + cxFuncs.storage = chrome.storage.sync; + cxFuncs.getAllNickNames = function (callback) { + var nickNames = []; + this.storage.get(null, function (items) { + for (var key in items) { + if (items.hasOwnProperty(key)) { + var tobj = JSON.parse(items[key]); + if (tobj.type == 'wallet' || tobj.type == 'watchOnly') { + nickNames.push(tobj.nick); + nickNames.push(key); + } + } } - } - return result; + callback(nickNames); + }); }; - - /** - * Translates with the usage of the fallback languages. - * - * @param translationId - * @param interpolateParams - * @param Interpolator - * @returns {Q.promise} Promise, that resolves to the translation. - */ - var fallbackTranslation = function (translationId, interpolateParams, Interpolator, defaultTranslationText) { - // Start with the fallbackLanguage with index 0 - return resolveForFallbackLanguage((startFallbackIteration > 0 ? startFallbackIteration : fallbackIndex), translationId, interpolateParams, Interpolator, defaultTranslationText); + cxFuncs.addWalletToStorage = function (address, encStr, nickname, callback) { + nickname = nickname.replace(/(<([^>]+)>)/ig, ""); + var value = { + nick: nickname, + priv: encStr, + type: 'wallet' + }; + var keyname = ethUtil.toChecksumAddress(address); + var obj = {}; + obj[keyname] = JSON.stringify(value); + this.storage.set(obj, callback); }; - - /** - * Translates with the usage of the fallback languages. - * - * @param translationId - * @param interpolateParams - * @param Interpolator - * @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); + cxFuncs.addWatchOnlyAddress = function (address, nickname, callback) { + nickname = nickname.replace(/(<([^>]+)>)/ig, ""); + var value = { + nick: nickname, + type: 'watchOnly' + }; + var keyname = ethUtil.toChecksumAddress(address);; + var obj = {}; + obj[keyname] = JSON.stringify(value); + this.storage.set(obj, callback); }; - - var determineTranslation = function (translationId, interpolateParams, interpolationId, defaultTranslationText, uses) { - - 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)) { - 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'); - 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) - .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)); + cxFuncs.getStorageArr = function (filter, callback) { + var wallets = []; + this.storage.get(null, function (items) { + for (var key in items) { + if (items.hasOwnProperty(key)) { + var tobj = JSON.parse(items[key]); + if (tobj.type == filter) { + tobj['addr'] = key; + wallets.push(tobj); + } } } - } - 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]; - } + wallets.sort(function (a, b) { + if (a.nick < b.nick) return -1; + if (a.nick > b.nick) return 1; + return 0; + }); + callback(wallets); + }); + }; + cxFuncs.getWalletsArr = function (callback) { + this.getStorageArr('wallet', callback); + }; + cxFuncs.getWatchOnlyArr = function (callback) { + this.getStorageArr('watchOnly', callback); + }; + cxFuncs.deleteAccount = function (address, callback) { + this.storage.remove(address, function () { + callback(address); + }); + }; + cxFuncs.editNickName = function (address, newNick, callback) { + newNick = newNick.replace(/(<([^>]+)>)/ig, ""); + this.storage.get(address, function (account) { + var accountInfo = account[address]; + accountInfo = JSON.parse(accountInfo); + accountInfo['nick'] = newNick; + account[address] = JSON.stringify(accountInfo); + cxFuncs.storage.set(account, function () { + callback(newNick); + }); + }); + }; + module.exports = cxFuncs; + }, {}], 21: [function (require, module, exports) { + 'use strict'; + + var QRCodeDrtv = function QRCodeDrtv() { + return function (scope, element, attrs) { + var watchVar = attrs.watchVar; + scope.$watch(watchVar, function () { + var value = attrs.qrCode; + element.empty(); + var delay = 0; + if (element[0].clientWidth == 0) delay = 200; + setTimeout(function () { + new QRCode(element[0], { + text: value, + width: element[0].clientWidth, + height: element[0].clientWidth, + colorDark: "#000000", + colorLight: "#ffffff", + correctLevel: QRCode.CorrectLevel.H + }); + }, delay); + }); + }; + }; + module.exports = QRCodeDrtv; + }, {}], 22: [function (require, module, exports) { + 'use strict'; + + var blockiesDrtv = function blockiesDrtv() { + return function (scope, element, attrs) { + var watchVar = attrs.watchVar; + scope.$watch(watchVar, function () { + var address = attrs.blockieAddress; + var content = ethFuncs.validateEtherAddress(address) ? globalFuncs.getBlockie(address) : ''; + element.css({ + 'background-image': 'url(' + content + ')' + }); + }); + }; + }; + module.exports = blockiesDrtv; + }, {}], 23: [function (require, module, exports) { + 'use strict'; - // if the translation id exists, we can just interpolate it - if (table && Object.prototype.hasOwnProperty.call(table, translationId)) { - var translation = table[translationId]; + var cxWalletDecryptDrtv = function cxWalletDecryptDrtv() { + return { + restrict: "E", + template: '
Your file is encrypted. Please enter the password:
\n \ + \n \ +Your file is encrypted. Please enter the password:
\n \ + \n \ +' + str + '
'; + }; + globalFuncs.getDangerText = function (str) { + return '' + str + '
'; + }; + globalFuncs.errorMsgs = ["Please enter valid amount.", "Your password must be at least 9 characters. Please ensure it is a strong password. ", "Sorry! We don\'t recognize this type of wallet file. ", "This is not a valid wallet file. ", "This unit doesn\'t exists, please use the one of the following units ", "Invalid address. ", "Invalid password. ", "Invalid amount. ", "Invalid gas limit. ", "Invalid data value. ", "Invalid gas amount. ", // 10 + "Invalid nonce. ", "Invalid signed transaction. ", "A wallet with this nickname already exists. ", "Wallet not found. ", "Whoops. It doesnt look like a proposal with this ID exists yet or there is an error reading this proposal. ", // 15 + "A wallet with this address already exists in storage. Please check your wallets page. ", "You need to have at least 0.01 ETH in your account to cover the cost of gas. Please add some ETH and try again. ", "All gas would be used on this transaction. This means you have already voted on this proposal or the debate period has ended.", "Invalid symbol", "Not a valid ERC-20 token", "Could not estimate gas. There are not enough funds in the account, or the receiving contract address would throw an error. Feel free to manually set the gas and proceed. The error message upon sending may be more informative."]; + globalFuncs.successMsgs = ["Valid address", "Wallet successfully decrypted", "Transaction submitted. TX ID: ", "Your wallet was successfully added: ", "You have successfully voted. Thank you for being an active participant in The DAO.", "File Selected: "]; + globalFuncs.gethErrors = { + "Invalid sender": "GETH_InvalidSender", + "Nonce too low": "GETH_Nonce", + "Gas price too low for acceptance": "GETH_Cheap", + "Insufficient balance": "GETH_Balance", + "Account does not exist or account balance too low": "GETH_NonExistentAccount", + "Insufficient funds for gas * price + value": "GETH_InsufficientFunds", + "Intrinsic gas too low": "GETH_IntrinsicGas", + "Exceeds block gas limit": "GETH_GasLimit", + "Negative value": "GETH_NegativeValue" }; + globalFuncs.gethErrorMsgs = {}; + globalFuncs.getGethMsg = function (str) { + if (str in this.gethErrors) { + var key = this.gethErrors[str]; + if (key in this.gethErrorMsgs) { + return this.gethErrorMsgs[key]; } - $translate.use($translate.use()); - } - if ($fallbackWasString) { - return $fallbackLanguage[0]; - } else { - return $fallbackLanguage; } - + return str; }; - - /** - * @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; + globalFuncs.parityErrors = { + "Transaction with the same hash was already imported\\.": "PARITY_AlreadyImported", + "Transaction nonce is too low\\. Try incrementing the nonce\\.": "PARITY_Old", + "Transaction fee is too low\\. There is another transaction with same nonce in the queue\\. Try increasing the fee or incrementing the nonce\\.": "PARITY_TooCheapToReplace", + "There are too many transactions in the queue\\. Your transaction was dropped due to limit\\. Try increasing the fee\\.": "PARITY_LimitReached", + "Transaction fee is too low\\. It does not satisfy your node's minimal fee \\(minimal: (\\d+), got: (\\d+)\\)\\. Try increasing the fee\\.": "PARITY_InsufficientGasPrice", + "Insufficient funds\\. Account you try to send transaction from does not have enough funds\\. Required (\\d+) and got: (\\d+)\\.": "PARITY_InsufficientBalance", + "Transaction cost exceeds current gas limit\\. Limit: (\\d+), got: (\\d+)\\. Try decreasing supplied gas\\.": "PARITY_GasLimitExceeded", + "Supplied gas is beyond limit\\.": "PARITY_InvalidGasLimit" }; + globalFuncs.parityErrorMsgs = {}; + globalFuncs.getParityMsg = function (str) { + for (var reg in this.parityErrors) { + var args = str.match("^" + reg + "$"); + if (args) { + var key = this.parityErrors[reg]; + if (key in this.parityErrorMsgs) { + args[0] = this.parityErrorMsgs[key]; + return format.apply(this, args); } } - } - + return str; }; - - /** - * @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; + globalFuncs.getEthNodeName = function () { + // return "geth"; + return "parity"; }; - - /** - * @ngdoc function - * @name pascalprecht.translate.$translate#storage - * @methodOf pascalprecht.translate.$translate - * - * @description - * Returns registered storage. - * - * @return {object} Storage - */ - $translate.storage = function () { - return Storage; + globalFuncs.getEthNodeMsg = function (str) { + var ethNode = this.getEthNodeName(); + if (ethNode == "geth") return this.getGethMsg(str);else return this.getParityMsg(str); + }; + globalFuncs.scrypt = { + n: 1024 + }; + globalFuncs.postDelay = 300; + globalFuncs.kdf = "scrypt"; + globalFuncs.defaultTxGasLimit = 21000; + globalFuncs.digixClaimTxGasLimit = 150000; + globalFuncs.donateAddress = "0x7cB57B5A97eAbe94205C07890BE4c1aD31E486A8"; + globalFuncs.isNumeric = function (n) { + return !isNaN(parseFloat(n)) && isFinite(n); + }; + globalFuncs.urlGet = function (name) { + name = name.toLowerCase(); + if (name = new RegExp('[?&]' + encodeURIComponent(name) + '=([^&]*)').exec(location.search.toLowerCase())) return this.stripTags(decodeURIComponent(name[1])); + }; + globalFuncs.stripTags = function (str) { + var SCRIPT_REGEX = / +Directive | +How | +Source | +Rendered | +
ng-bind-html | +Automatically uses $sanitize | +<div ng-bind-html="snippet"> |
+ + |
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"> |
+ + |
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')).getInnerHtml()). + toBe("an html\n" + + "click here\n" + + "snippet
"); + }); + + it('should escape snippet without any filter', function() { + expect(element(by.css('#bind-default div')).getInnerHtml()). + 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')).getInnerHtml()). + toBe('new text'); + expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe( + 'new text'); + expect(element(by.css('#bind-default div')).getInnerHtml()).toBe( + "new <b onclick=\"alert(1)\">text</b>"); + }); +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;
+ * }
+ *
+ * Filter | +Source | +Rendered | +
---|---|---|
linky filter | +
+ <div ng-bind-html="snippet | linky">+ |
+ + + | +
linky target | +
+ <div ng-bind-html="snippetWithSingleURL | linky:'_blank'">+ |
+ + + | +
linky custom attributes | +
+ <div ng-bind-html="snippetWithSingleURL | linky:'_self':{rel: 'nofollow'}">+ |
+ + + | +
no filter | +<div ng-bind="snippet"> |
+ + |
{{ 'TRANSLATION_ID' | translate }}-
{{ translationId | translate }}-
{{ 'WITH_VALUES' | translate:'{value: 5}' }}-
{{ 'WITH_VALUES' | translate:values }}+ function addText(text) { + if (!text) { + return; + } + html.push(sanitizeText(text)); + } -
form = {{user | json}}-
master = {{master | json}}-
This renders because the controller does not fail to - instantiate, by using explicit annotation style (see - script.js for details) -
-This renders because the controller does not fail to - instantiate, by using explicit annotation style - (see script.js for details) -
-The controller could not be instantiated, due to relying - on automatic function annotations (which are disabled in - strict mode). As such, the content of this section is not - interpolated, and there should be an error in your web console. -
-+ * // 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 translations(langKey, translationTable) { - /** - * @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 (!langKey && !translationTable) { + return $translationTable; } - }; - if (configFn) { - config(configFn); - } + 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; + }; - return moduleInstance; + this.translations = translations; /** - * @param {string} provider - * @param {string} method - * @param {String=} insertMethod - * @returns {angular.Module} + * @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 */ - function invokeLater(provider, method, insertMethod, queue) { - if (!queue) queue = invokeQueue; - return function() { - queue[insertMethod || 'push']([provider, method, arguments]); - return moduleInstance; - }; - } + this.cloakClassName = function (name) { + if (!name) { + return $cloakClassName; + } + $cloakClassName = name; + return this; + }; /** - * @param {string} provider - * @param {string} method - * @returns {angular.Module} + * @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 */ - function invokeLaterAndSetModuleName(provider, method) { - return function(recipeName, factoryFunction) { - if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; - invokeQueue.push([provider, method, arguments]); - return moduleInstance; - }; - } - }); - }; - }); + 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 flatObject(data, path, result, prevKey) { + var key, keyWithPath, keyWithShortPath, val; -/* global shallowCopy: true */ + 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'; -/** - * 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 || []; + /** + * @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; + }; - for (var i = 0, ii = src.length; i < ii; i++) { - dst[i] = src[i]; - } - } else if (isObject(src)) { - dst = dst || {}; + /** + * @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'); + }; - for (var key in src) { - if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { - dst[key] = src[key]; - } - } - } + /** + * @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; + }; - return dst || src; -} + /** + * @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; + }; -/* global toDebugString: true */ + /** + * @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 setupPreferredLanguage(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; + }; -function serializeObject(obj) { - var seen = []; + /** + * 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; + }; - return JSON.stringify(obj, function(key, val) { - val = toJsonReplacer(key, val); - if (isObject(val)) { + /** + * 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; + }; - if (seen.indexOf(val) >= 0) return '...'; + /** + * @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; + }; - seen.push(val); - } - return val; - }); -} - -function toDebugString(obj) { - if (typeof obj === 'function') { - return obj.toString().replace(/ \{[\s\S]*$/, ''); - } else if (isUndefined(obj)) { - return 'undefined'; - } else if (typeof obj !== 'string') { - return serializeObject(obj); - } - return obj; -} - -/* global angularModule: true, - version: true, - - $CompileProvider, - - htmlAnchorDirective, - inputDirective, - inputDirective, - formDirective, - scriptDirective, - selectDirective, - styleDirective, - optionDirective, - ngBindDirective, - ngBindHtmlDirective, - ngBindTemplateDirective, - ngClassDirective, - ngClassEvenDirective, - ngClassOddDirective, - ngCloakDirective, - ngControllerDirective, - ngFormDirective, - ngHideDirective, - ngIfDirective, - ngIncludeDirective, - ngIncludeFillContentDirective, - ngInitDirective, - ngNonBindableDirective, - ngPluralizeDirective, - ngRepeatDirective, - ngShowDirective, - ngStyleDirective, - ngSwitchDirective, - ngSwitchWhenDirective, - ngSwitchDefaultDirective, - ngOptionsDirective, - ngTranscludeDirective, - ngModelDirective, - ngListDirective, - ngChangeDirective, - patternDirective, - patternDirective, - requiredDirective, - requiredDirective, - minlengthDirective, - minlengthDirective, - maxlengthDirective, - maxlengthDirective, - ngValueDirective, - ngModelOptionsDirective, - ngAttributeAliasDirectives, - ngEventDirectives, - - $AnchorScrollProvider, - $AnimateProvider, - $CoreAnimateCssProvider, - $$CoreAnimateJsProvider, - $$CoreAnimateQueueProvider, - $$AnimateRunnerFactoryProvider, - $$AnimateAsyncRunFactoryProvider, - $BrowserProvider, - $CacheFactoryProvider, - $ControllerProvider, - $DateProvider, - $DocumentProvider, - $ExceptionHandlerProvider, - $FilterProvider, - $$ForceReflowProvider, - $InterpolateProvider, - $IntervalProvider, - $$HashMapProvider, - $HttpProvider, - $HttpParamSerializerProvider, - $HttpParamSerializerJQLikeProvider, - $HttpBackendProvider, - $xhrFactoryProvider, - $jsonpCallbacksProvider, - $LocationProvider, - $LogProvider, - $ParseProvider, - $RootScopeProvider, - $QProvider, - $$QProvider, - $$SanitizeUriProvider, - $SceProvider, - $SceDelegateProvider, - $SnifferProvider, - $TemplateCacheProvider, - $TemplateRequestProvider, - $$TestabilityProvider, - $TimeoutProvider, - $$RAFProvider, - $WindowProvider, - $$jqLiteProvider, - $$CookieReaderProvider -*/ - - -/** - * @ngdoc object - * @name angular.version - * @module ng - * @description - * An object that contains information about the current AngularJS version. - * - * This object has the following properties: - * - * - `full` – `{string}` – Full version string, such as "0.9.18". - * - `major` – `{number}` – Major version number, such as "0". - * - `minor` – `{number}` – Minor version number, such as "9". - * - `dot` – `{number}` – Dot version number, such as "18". - * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". - */ -var version = { - full: '1.5.8', // all of these placeholder strings will be replaced by grunt's - major: 1, // package task - minor: 5, - dot: 8, - codeName: 'arbitrary-fallbacks' -}; - - -function publishExternalAPI(angular) { - extend(angular, { - 'bootstrap': bootstrap, - 'copy': copy, - 'extend': extend, - 'merge': merge, - 'equals': equals, - 'element': jqLite, - 'forEach': forEach, - 'injector': createInjector, - 'noop': noop, - 'bind': bind, - 'toJson': toJson, - 'fromJson': fromJson, - 'identity': identity, - 'isUndefined': isUndefined, - 'isDefined': isDefined, - 'isString': isString, - 'isFunction': isFunction, - 'isObject': isObject, - 'isNumber': isNumber, - 'isElement': isElement, - 'isArray': isArray, - 'version': version, - 'isDate': isDate, - 'lowercase': lowercase, - 'uppercase': uppercase, - 'callbacks': {$$counter: 0}, - 'getTestability': getTestability, - '$$minErr': minErr, - '$$csp': csp, - 'reloadWithDebugInfo': reloadWithDebugInfo - }); - - angularModule = setupModuleLoader(window); - - angularModule('ng', ['ngLocale'], ['$provide', - function ngModule($provide) { - // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. - $provide.provider({ - $$sanitizeUri: $$SanitizeUriProvider - }); - $provide.provider('$compile', $CompileProvider). - directive({ - a: htmlAnchorDirective, - input: inputDirective, - textarea: inputDirective, - form: formDirective, - script: scriptDirective, - select: selectDirective, - style: styleDirective, - option: optionDirective, - ngBind: ngBindDirective, - ngBindHtml: ngBindHtmlDirective, - ngBindTemplate: ngBindTemplateDirective, - ngClass: ngClassDirective, - ngClassEven: ngClassEvenDirective, - ngClassOdd: ngClassOddDirective, - ngCloak: ngCloakDirective, - ngController: ngControllerDirective, - ngForm: ngFormDirective, - ngHide: ngHideDirective, - ngIf: ngIfDirective, - ngInclude: ngIncludeDirective, - ngInit: ngInitDirective, - ngNonBindable: ngNonBindableDirective, - ngPluralize: ngPluralizeDirective, - ngRepeat: ngRepeatDirective, - ngShow: ngShowDirective, - ngStyle: ngStyleDirective, - ngSwitch: ngSwitchDirective, - ngSwitchWhen: ngSwitchWhenDirective, - ngSwitchDefault: ngSwitchDefaultDirective, - ngOptions: ngOptionsDirective, - ngTransclude: ngTranscludeDirective, - ngModel: ngModelDirective, - ngList: ngListDirective, - ngChange: ngChangeDirective, - pattern: patternDirective, - ngPattern: patternDirective, - required: requiredDirective, - ngRequired: requiredDirective, - minlength: minlengthDirective, - ngMinlength: minlengthDirective, - maxlength: maxlengthDirective, - ngMaxlength: maxlengthDirective, - ngValue: ngValueDirective, - ngModelOptions: ngModelOptionsDirective - }). - directive({ - ngInclude: ngIncludeFillContentDirective - }). - directive(ngAttributeAliasDirectives). - directive(ngEventDirectives); - $provide.provider({ - $anchorScroll: $AnchorScrollProvider, - $animate: $AnimateProvider, - $animateCss: $CoreAnimateCssProvider, - $$animateJs: $$CoreAnimateJsProvider, - $$animateQueue: $$CoreAnimateQueueProvider, - $$AnimateRunner: $$AnimateRunnerFactoryProvider, - $$animateAsyncRun: $$AnimateAsyncRunFactoryProvider, - $browser: $BrowserProvider, - $cacheFactory: $CacheFactoryProvider, - $controller: $ControllerProvider, - $document: $DocumentProvider, - $exceptionHandler: $ExceptionHandlerProvider, - $filter: $FilterProvider, - $$forceReflow: $$ForceReflowProvider, - $interpolate: $InterpolateProvider, - $interval: $IntervalProvider, - $http: $HttpProvider, - $httpParamSerializer: $HttpParamSerializerProvider, - $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider, - $httpBackend: $HttpBackendProvider, - $xhrFactory: $xhrFactoryProvider, - $jsonpCallbacks: $jsonpCallbacksProvider, - $location: $LocationProvider, - $log: $LogProvider, - $parse: $ParseProvider, - $rootScope: $RootScopeProvider, - $q: $QProvider, - $$q: $$QProvider, - $sce: $SceProvider, - $sceDelegate: $SceDelegateProvider, - $sniffer: $SnifferProvider, - $templateCache: $TemplateCacheProvider, - $templateRequest: $TemplateRequestProvider, - $$testability: $$TestabilityProvider, - $timeout: $TimeoutProvider, - $window: $WindowProvider, - $$rAF: $$RAFProvider, - $$jqLite: $$jqLiteProvider, - $$HashMap: $$HashMapProvider, - $$cookieReader: $$CookieReaderProvider - }); - } - ]); -} - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * 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? * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -/* global JQLitePrototype: true, - addEventListenerFn: true, - removeEventListenerFn: true, - BOOLEAN_ATTR: true, - ALIASED_ATTR: true, -*/ - -////////////////////////////////// -//JQLite -////////////////////////////////// - -/** - * @ngdoc function - * @name angular.element - * @module ng - * @kind function - * - * @description - * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. - * - * If jQuery is available, `angular.element` is an alias for the - * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` - * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or **jqLite**. - * - * jqLite is a tiny, API-compatible subset of jQuery that allows - * Angular to manipulate the DOM in a cross-browser compatible way. jqLite implements only the most - * commonly needed functionality with the goal of having a very small footprint. - * - * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. You can also use the - * {@link ngJq `ngJq`} directive to specify that jqlite should be used over jQuery, or to use a - * specific version of jQuery if multiple versions exist on the page. - * - *
+ * 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; + }; - if (!type) { - for (type in events) { - if (type !== '$destroy') { - removeEventListenerFn(element, type, handle); - } - delete events[type]; - } - } else { + /** + * @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; + }; - var removeHandler = function(type) { - var listenerFns = events[type]; - if (isDefined(fn)) { - arrayRemove(listenerFns || [], fn); - } - if (!(isDefined(fn) && listenerFns && listenerFns.length > 0)) { - removeEventListenerFn(element, type, handle); - delete events[type]; - } - }; + /** + * @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; + }; - forEach(type.split(' '), function(type) { - removeHandler(type); - if (MOUSE_EVENT_MAP[type]) { - removeHandler(MOUSE_EVENT_MAP[type]); - } - }); - } -} + /** + * @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) { -function jqLiteRemoveData(element, name) { - var expandoId = element.ng339; - var expandoStore = expandoId && jqCache[expandoId]; + if (!options) { + options = {}; + } else if (angular.isString(options)) { + options = { + standard: options + }; + } - if (expandoStore) { - if (name) { - delete expandoStore.data[name]; - return; - } + uniformLanguageTagResolver = options.standard; - if (expandoStore.handle) { - if (expandoStore.events.$destroy) { - expandoStore.handle({}, '$destroy'); - } - jqLiteOff(element); - } - delete jqCache[expandoId]; - element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it - } -} + 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) { -function jqLiteExpandoStore(element, createIfNecessary) { - var expandoId = element.ng339, - expandoStore = expandoId && jqCache[expandoId]; + var locale = fn && angular.isFunction(fn) ? fn() : getLocale(); - if (createIfNecessary && !expandoStore) { - element.ng339 = expandoId = jqNextId(); - expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined}; - } + if (!$availableLanguageKeys.length) { + $preferredLanguage = locale; + } else { + $preferredLanguage = negotiateLocale(locale) || locale; + } - return expandoStore; -} + 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; + }; -function jqLiteData(element, key, value) { - if (jqLiteAcceptsData(element)) { + /** + * @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; + }; - var isSimpleSetter = isDefined(value); - var isSimpleGetter = !isSimpleSetter && key && !isObject(key); - var massGetter = !key; - var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter); - var data = expandoStore && expandoStore.data; + /** + * @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; + } + }; - if (isSimpleSetter) { // data('key', value) - data[key] = value; - } else { - if (massGetter) { // data() - return data; - } else { - if (isSimpleGetter) { // data('key') - // don't force creation of expandoStore if it doesn't exist yet - return data && data[key]; - } else { // mass-setter: data({key1: val1, key2: val2}) - extend(data, key); - } - } - } - } -} - -function jqLiteHasClass(element, selector) { - if (!element.getAttribute) return false; - return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). - indexOf(" " + selector + " ") > -1); -} - -function jqLiteRemoveClass(element, cssClasses) { - if (cssClasses && element.setAttribute) { - forEach(cssClasses.split(' '), function(cssClass) { - element.setAttribute('class', trim( - (" " + (element.getAttribute('class') || '') + " ") - .replace(/[\n\t]/g, " ") - .replace(" " + trim(cssClass) + " ", " ")) - ); - }); - } -} + /** + * @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; + } + }; -function jqLiteAddClass(element, cssClasses) { - if (cssClasses && element.setAttribute) { - var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') - .replace(/[\n\t]/g, " "); + /** + * @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; + }; - forEach(cssClasses.split(' '), function(cssClass) { - cssClass = trim(cssClass); - if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { - existingClasses += cssClass + ' '; - } - }); + /** + * @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; + }; - element.setAttribute('class', trim(existingClasses)); - } -} + /** + * @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 $translate(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); + } -function jqLiteAddNodes(root, elements) { - // THIS CODE IS VERY HOT. Don't make changes without benchmarking. + // 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 translateAll(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 translate(translationId) { + var deferred = $q.defer(); + var regardless = function regardless(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); + } - if (elements) { + var deferred = $q.defer(); - // if a Node (the most common case) - if (elements.nodeType) { - root[root.length++] = elements; - } else { - var length = elements.length; + // trim off any whitespace + if (translationId) { + translationId = trim.apply(translationId); + } - // if an Array or NodeList and not a Window - if (typeof length === 'number' && elements.window !== elements) { - if (length) { - for (var i = 0; i < length; i++) { - root[root.length++] = elements[i]; - } - } - } else { - root[root.length++] = elements; - } - } - } -} + 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 promiseResolved() { + // $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); + } + return deferred.promise; + }; -function jqLiteController(element, name) { - return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); -} + /** + * @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 applyNotFoundIndicators(translationId) { + // applying notFoundIndicators + if ($notFoundIndicatorLeft) { + translationId = [$notFoundIndicatorLeft, translationId].join(' '); + } + if ($notFoundIndicatorRight) { + translationId = [translationId, $notFoundIndicatorRight].join(' '); + } + return translationId; + }; -function jqLiteInheritedData(element, name, value) { - // if element is the document object work with the html element instead - // this makes $(document).scope() possible - if (element.nodeType == NODE_TYPE_DOCUMENT) { - element = element.documentElement; - } - var names = isArray(name) ? name : [name]; + /** + * @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 useLanguage(key) { + $uses = key; - while (element) { - for (var i = 0, ii = names.length; i < ii; i++) { - if (isDefined(value = jqLite.data(element, names[i]))) return value; - } + // make sure to store new language key before triggering success event + if ($storageFactory) { + Storage.put($translate.storageKey(), $uses); + } - // If dealing with a document fragment node with a host element, and no parent, use the host - // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM - // to lookup parent controllers. - element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host); - } -} - -function jqLiteEmpty(element) { - jqLiteDealoc(element, true); - while (element.firstChild) { - element.removeChild(element.firstChild); - } -} - -function jqLiteRemove(element, keepData) { - if (!keepData) jqLiteDealoc(element); - var parent = element.parentNode; - if (parent) parent.removeChild(element); -} - - -function jqLiteDocumentLoaded(action, win) { - win = win || window; - if (win.document.readyState === 'complete') { - // Force the action to be run async for consistent behavior - // from the action's point of view - // i.e. it will definitely not be in a $apply - win.setTimeout(action); - } else { - // No need to unbind this handler as load is only ever called once - jqLite(win).on('load', action); - } -} - -////////////////////////////////////////// -// Functions which are declared directly. -////////////////////////////////////////// -var JQLitePrototype = JQLite.prototype = { - ready: function(fn) { - var fired = false; - - function trigger() { - if (fired) return; - fired = true; - fn(); - } + $rootScope.$emit('$translateChangeSuccess', { language: key }); - // check if document is already loaded - if (window.document.readyState === 'complete') { - window.setTimeout(trigger); - } else { - this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 - // we can not use jqLite since we are not done loading and jQuery could be loaded later. - // jshint -W064 - JQLite(window).on('load', trigger); // fallback to window.onload for others - // jshint +W064 - } - }, - toString: function() { - var value = []; - forEach(this, function(e) { value.push('' + e);}); - return '[' + value.join(', ') + ']'; - }, - - eq: function(index) { - return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); - }, - - length: 0, - push: push, - sort: [].sort, - splice: [].splice -}; - -////////////////////////////////////////// -// Functions iterating getter/setters. -// these functions return self on setter and -// value on get. -////////////////////////////////////////// -var BOOLEAN_ATTR = {}; -forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { - BOOLEAN_ATTR[lowercase(value)] = value; -}); -var BOOLEAN_ELEMENTS = {}; -forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { - BOOLEAN_ELEMENTS[value] = true; -}); -var ALIASED_ATTR = { - 'ngMinlength': 'minlength', - 'ngMaxlength': 'maxlength', - 'ngMin': 'min', - 'ngMax': 'max', - 'ngPattern': 'pattern' -}; - -function getBooleanAttrName(element, name) { - // check dom last since we will most likely fail on name - var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; - - // booleanAttr is here twice to minimize DOM access - return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr; -} - -function getAliasedAttrName(name) { - return ALIASED_ATTR[name]; -} - -forEach({ - data: jqLiteData, - removeData: jqLiteRemoveData, - hasData: jqLiteHasData, - cleanData: jqLiteCleanData -}, function(fn, name) { - JQLite[name] = fn; -}); - -forEach({ - data: jqLiteData, - inheritedData: jqLiteInheritedData, - - scope: function(element) { - // Can't use jqLiteData here directly so we stay compatible with jQuery! - return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); - }, - - isolateScope: function(element) { - // Can't use jqLiteData here directly so we stay compatible with jQuery! - return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate'); - }, - - controller: jqLiteController, - - injector: function(element) { - return jqLiteInheritedData(element, '$injector'); - }, - - removeAttr: function(element, name) { - element.removeAttribute(name); - }, - - hasClass: jqLiteHasClass, - - css: function(element, name, value) { - name = camelCase(name); - - if (isDefined(value)) { - element.style[name] = value; - } else { - return element.style[name]; - } - }, + // inform default interpolator + defaultInterpolator.setLocale($uses); - attr: function(element, name, value) { - var nodeType = element.nodeType; - if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) { - return; - } - var lowercasedName = lowercase(name); - if (BOOLEAN_ATTR[lowercasedName]) { - if (isDefined(value)) { - if (!!value) { - element[name] = true; - element.setAttribute(name, lowercasedName); - } else { - element[name] = false; - element.removeAttribute(lowercasedName); - } - } else { - return (element[name] || - (element.attributes.getNamedItem(name) || noop).specified) - ? lowercasedName - : undefined; - } - } else if (isDefined(value)) { - element.setAttribute(name, value); - } else if (element.getAttribute) { - // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code - // some elements (e.g. Document) don't have get attribute, so return undefined - var ret = element.getAttribute(name, 2); - // normalize non-existing attributes to undefined (as jQuery) - return ret === null ? undefined : ret; - } - }, + var eachInterpolator = function eachInterpolator(interpolator, id) { + interpolatorHashMap[id].setLocale($uses); + }; + eachInterpolator.displayName = 'eachInterpolatorLocaleSetter'; - prop: function(element, name, value) { - if (isDefined(value)) { - element[name] = value; - } else { - return element[name]; - } - }, + // inform all others too! + angular.forEach(interpolatorHashMap, eachInterpolator); + $rootScope.$emit('$translateChangeEnd', { language: key }); + }; - text: (function() { - getText.$dv = ''; - return getText; + /** + * @name loadAsync + * @private + * + * @description + * Kicks of 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 loadAsync(key) { + if (!key) { + throw 'No language key specified for loading.'; + } - function getText(element, value) { - if (isUndefined(value)) { - var nodeType = element.nodeType; - return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : ''; - } - element.textContent = value; - } - })(), + var deferred = $q.defer(); - val: function(element, value) { - if (isUndefined(value)) { - if (element.multiple && nodeName_(element) === 'select') { - var result = []; - forEach(element.options, function(option) { - if (option.selected) { - result.push(option.value || option.text); - } - }); - return result.length === 0 ? null : result; - } - return element.value; - } - element.value = value; - }, + $rootScope.$emit('$translateLoadingStart', { language: key }); + pendingLoader = true; - html: function(element, value) { - if (isUndefined(value)) { - return element.innerHTML; - } - jqLiteDealoc(element, true); - element.innerHTML = value; - }, - - empty: jqLiteEmpty -}, function(fn, name) { - /** - * Properties: writes return selection, reads return first value - */ - JQLite.prototype[name] = function(arg1, arg2) { - var i, key; - var nodeCount = this.length; - - // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it - // in a way that survives minification. - // jqLiteEmpty takes no arguments but is a setter. - if (fn !== jqLiteEmpty && - (isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) { - if (isObject(arg1)) { - - // we are a write, but the object properties are the key/values - for (i = 0; i < nodeCount; i++) { - if (fn === jqLiteData) { - // data() takes the whole object in jQuery - fn(this[i], arg1); - } else { - for (key in arg1) { - fn(this[i], key, arg1[key]); + var cache = loaderCache; + if (typeof cache === 'string') { + // getting on-demand instance of loader + cache = $injector.get(cache); } - } - } - // return self for chaining - return this; - } else { - // we are a read, so read the first child. - // TODO: do we still need this? - var value = fn.$dv; - // Only if we have $dv do we iterate over all, otherwise it is just the first element. - var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount; - for (var j = 0; j < jj; j++) { - var nodeValue = fn(this[j], arg1, arg2); - value = value ? value + nodeValue : nodeValue; - } - return value; - } - } else { - // we are a write, so apply to all children - for (i = 0; i < nodeCount; i++) { - fn(this[i], arg1, arg2); - } - // return self for chaining - return this; - } - }; -}); -function createEventHandler(element, events) { - var eventHandler = function(event, type) { - // jQuery specific api - event.isDefaultPrevented = function() { - return event.defaultPrevented; - }; + var loaderOptions = angular.extend({}, $loaderOptions, { + key: key, + $http: angular.extend({}, { + cache: cache + }, $loaderOptions.$http) + }); - var eventFns = events[type || event.type]; - var eventFnsLength = eventFns ? eventFns.length : 0; + var onLoaderSuccess = function onLoaderSuccess(data) { + var translationTable = {}; + $rootScope.$emit('$translateLoadingSuccess', { language: key }); - if (!eventFnsLength) return; + 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'; - if (isUndefined(event.immediatePropagationStopped)) { - var originalStopImmediatePropagation = event.stopImmediatePropagation; - event.stopImmediatePropagation = function() { - event.immediatePropagationStopped = true; + var onLoaderError = function onLoaderError(key) { + $rootScope.$emit('$translateLoadingError', { language: key }); + deferred.reject(key); + $rootScope.$emit('$translateLoadingEnd', { language: key }); + }; + onLoaderError.displayName = 'onLoaderError'; - if (event.stopPropagation) { - event.stopPropagation(); - } + $injector.get($loaderFactory)(loaderOptions).then(onLoaderSuccess, onLoaderError); - if (originalStopImmediatePropagation) { - originalStopImmediatePropagation.call(event); - } - }; - } + return deferred.promise; + }; - event.isImmediatePropagationStopped = function() { - return event.immediatePropagationStopped === true; - }; + if ($storageFactory) { + Storage = $injector.get($storageFactory); - // Some events have special handlers that wrap the real handler - var handlerWrapper = eventFns.specialHandlerWrapper || defaultHandlerWrapper; + if (!Storage.get || !Storage.put) { + throw new Error('Couldn\'t use storage \'' + $storageFactory + '\', missing get() or put() method!'); + } + } - // Copy event handlers in case event handlers array is modified during execution. - if ((eventFnsLength > 1)) { - eventFns = shallowCopy(eventFns); - } + // if we have additional interpolations that were added via + // $translateProvider.addInterpolation(), we have to map'em + if ($interpolatorFactories.length) { + var eachInterpolationFactory = function eachInterpolationFactory(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'; - for (var i = 0; i < eventFnsLength; i++) { - if (!event.isImmediatePropagationStopped()) { - handlerWrapper(element, event, eventFns[i]); - } - } - }; - - // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all - // events on `element` - eventHandler.elem = element; - return eventHandler; -} - -function defaultHandlerWrapper(element, event, handler) { - handler.call(element, event); -} - -function specialMouseHandlerWrapper(target, event, handler) { - // Refer to jQuery's implementation of mouseenter & mouseleave - // Read about mouseenter and mouseleave: - // http://www.quirksmode.org/js/events_mouse.html#link8 - var related = event.relatedTarget; - // For mousenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if (!related || (related !== target && !jqLiteContains.call(target, related))) { - handler.call(target, event); - } -} - -////////////////////////////////////////// -// Functions iterating traversal. -// These functions chain results into a single -// selector. -////////////////////////////////////////// -forEach({ - removeData: jqLiteRemoveData, - - on: function jqLiteOn(element, type, fn, unsupported) { - if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); - - // Do not add event handlers to non-elements because they will not be cleaned up. - if (!jqLiteAcceptsData(element)) { - return; - } - - var expandoStore = jqLiteExpandoStore(element, true); - var events = expandoStore.events; - var handle = expandoStore.handle; - - if (!handle) { - handle = expandoStore.handle = createEventHandler(element, events); - } - - // http://jsperf.com/string-indexof-vs-split - var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type]; - var i = types.length; + angular.forEach($interpolatorFactories, eachInterpolationFactory); + } - var addHandler = function(type, specialHandlerWrapper, noEventListener) { - var eventFns = events[type]; + /** + * @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 getTranslationTable(langKey) { + var deferred = $q.defer(); + if (Object.prototype.hasOwnProperty.call($translationTable, langKey)) { + deferred.resolve($translationTable[langKey]); + } else if (langPromises[langKey]) { + var onResolve = function onResolve(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; + }; - if (!eventFns) { - eventFns = events[type] = []; - eventFns.specialHandlerWrapper = specialHandlerWrapper; - if (type !== '$destroy' && !noEventListener) { - addEventListenerFn(element, type, handle); - } - } + /** + * @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 + * @returns {Q.promise} + */ + var getFallbackTranslation = function getFallbackTranslation(langKey, translationId, interpolateParams, Interpolator) { + var deferred = $q.defer(); - eventFns.push(fn); - }; + var onResolve = function onResolve(translationTable) { + if (Object.prototype.hasOwnProperty.call(translationTable, translationId)) { + Interpolator.setLocale(langKey); + var translation = translationTable[translationId]; + if (translation.substr(0, 2) === '@:') { + getFallbackTranslation(langKey, translation.substr(2), interpolateParams, Interpolator).then(deferred.resolve, deferred.reject); + } else { + var interpolatedValue = Interpolator.interpolate(translationTable[translationId], interpolateParams, 'service'); + interpolatedValue = applyPostProcessing(translationId, translationTable[translationId], interpolatedValue, interpolateParams, langKey); - while (i--) { - type = types[i]; - if (MOUSE_EVENT_MAP[type]) { - addHandler(MOUSE_EVENT_MAP[type], specialMouseHandlerWrapper); - addHandler(type, undefined, true); - } else { - addHandler(type); - } - } - }, + deferred.resolve(interpolatedValue); + } + Interpolator.setLocale($uses); + } else { + deferred.reject(); + } + }; + onResolve.displayName = 'fallbackTranslationResolver'; - off: jqLiteOff, + getTranslationTable(langKey).then(onResolve, deferred.reject); - one: function(element, type, fn) { - element = jqLite(element); + return deferred.promise; + }; - //add the listener twice so that when it is called - //you can remove the original function and still be - //able to call element.off(ev, fn) normally - element.on(type, function onFn() { - element.off(type, fn); - element.off(type, onFn); - }); - element.on(type, fn); - }, - - replaceWith: function(element, replaceNode) { - var index, parent = element.parentNode; - jqLiteDealoc(element); - forEach(new JQLite(replaceNode), function(node) { - if (index) { - parent.insertBefore(node, index.nextSibling); - } else { - parent.replaceChild(node, element); - } - index = node; - }); - }, + /** + * @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 getFallbackTranslationInstant(langKey, translationId, interpolateParams, Interpolator, sanitizeStrategy) { + var result, + translationTable = $translationTable[langKey]; + + if (translationTable && Object.prototype.hasOwnProperty.call(translationTable, translationId)) { + Interpolator.setLocale(langKey); + result = Interpolator.interpolate(translationTable[translationId], interpolateParams, 'filter', sanitizeStrategy); + 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); + } - children: function(element) { - var children = []; - forEach(element.childNodes, function(element) { - if (element.nodeType === NODE_TYPE_ELEMENT) { - children.push(element); - } - }); - return children; - }, + return result; + }; - contents: function(element) { - return element.contentDocument || element.childNodes || []; - }, + /** + * @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 translateByHandler(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; + } + }; - append: function(element, node) { - var nodeType = element.nodeType; - if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return; + /** + * @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 + * @returns {Q.promise} Promise that will resolve to the translation. + */ + var resolveForFallbackLanguage = function resolveForFallbackLanguage(fallbackLanguageIndex, translationId, interpolateParams, Interpolator, defaultTranslationText) { + var deferred = $q.defer(); - node = new JQLite(node); + if (fallbackLanguageIndex < $fallbackLanguage.length) { + var langKey = $fallbackLanguage[fallbackLanguageIndex]; + getFallbackTranslation(langKey, translationId, interpolateParams, Interpolator).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).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); - for (var i = 0, ii = node.length; i < ii; i++) { - var child = node[i]; - element.appendChild(child); - } - }, + // 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; + }; - prepend: function(element, node) { - if (element.nodeType === NODE_TYPE_ELEMENT) { - var index = element.firstChild; - forEach(new JQLite(node), function(child) { - element.insertBefore(child, index); - }); - } - }, + /** + * @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 resolveForFallbackLanguageInstant(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; + }; - wrap: function(element, wrapNode) { - jqLiteWrapNode(element, jqLite(wrapNode).eq(0).clone()[0]); - }, + /** + * Translates with the usage of the fallback languages. + * + * @param translationId + * @param interpolateParams + * @param Interpolator + * @returns {Q.promise} Promise, that resolves to the translation. + */ + var fallbackTranslation = function fallbackTranslation(translationId, interpolateParams, Interpolator, defaultTranslationText) { + // Start with the fallbackLanguage with index 0 + return resolveForFallbackLanguage(startFallbackIteration > 0 ? startFallbackIteration : fallbackIndex, translationId, interpolateParams, Interpolator, defaultTranslationText); + }; - remove: jqLiteRemove, + /** + * Translates with the usage of the fallback languages. + * + * @param translationId + * @param interpolateParams + * @param Interpolator + * @returns {String} translation + */ + var fallbackTranslationInstant = function fallbackTranslationInstant(translationId, interpolateParams, Interpolator, sanitizeStrategy) { + // Start with the fallbackLanguage with index 0 + return resolveForFallbackLanguageInstant(startFallbackIteration > 0 ? startFallbackIteration : fallbackIndex, translationId, interpolateParams, Interpolator, sanitizeStrategy); + }; - detach: function(element) { - jqLiteRemove(element, true); - }, + var determineTranslation = function determineTranslation(translationId, interpolateParams, interpolationId, defaultTranslationText, uses) { - after: function(element, newElement) { - var index = element, parent = element.parentNode; - newElement = new JQLite(newElement); + var deferred = $q.defer(); - for (var i = 0, ii = newElement.length; i < ii; i++) { - var node = newElement[i]; - parent.insertBefore(node, index.nextSibling); - index = node; - } - }, + var table = uses ? $translationTable[uses] : $translationTable, + Interpolator = interpolationId ? interpolatorHashMap[interpolationId] : defaultInterpolator; - addClass: jqLiteAddClass, - removeClass: jqLiteRemoveClass, + // if the translation id exists, we can just interpolate it + if (table && Object.prototype.hasOwnProperty.call(table, translationId)) { + var translation = table[translationId]; - toggleClass: function(element, selector, condition) { - if (selector) { - forEach(selector.split(' '), function(className) { - var classCondition = condition; - if (isUndefined(classCondition)) { - classCondition = !jqLiteHasClass(element, className); - } - (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); - }); - } - }, + // If using link, rerun $translate with linked translationId and return it + if (translation.substr(0, 2) === '@:') { - parent: function(element) { - var parent = element.parentNode; - return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null; - }, + $translate(translation.substr(2), interpolateParams, interpolationId, defaultTranslationText, uses).then(deferred.resolve, deferred.reject); + } else { + // + var resolvedTranslation = Interpolator.interpolate(translation, interpolateParams, 'service'); + 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); + } - next: function(element) { - return element.nextElementSibling; - }, + // 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).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; + }; - find: function(element, selector) { - if (element.getElementsByTagName) { - return element.getElementsByTagName(selector); - } else { - return []; - } - }, + var determineTranslationInstant = function determineTranslationInstant(translationId, interpolateParams, interpolationId, uses, sanitizeStrategy) { - clone: jqLiteClone, + var result, + table = uses ? $translationTable[uses] : $translationTable, + Interpolator = defaultInterpolator; - triggerHandler: function(element, event, extraParameters) { + // if the interpolation id exists use custom interpolator + if (interpolatorHashMap && Object.prototype.hasOwnProperty.call(interpolatorHashMap, interpolationId)) { + Interpolator = interpolatorHashMap[interpolationId]; + } - var dummyEvent, eventFnsCopy, handlerArgs; - var eventName = event.type || event; - var expandoStore = jqLiteExpandoStore(element); - var events = expandoStore && expandoStore.events; - var eventFns = events && events[eventName]; + // if the translation id exists, we can just interpolate it + if (table && Object.prototype.hasOwnProperty.call(table, translationId)) { + var translation = table[translationId]; - if (eventFns) { - // Create a dummy event to pass to the handlers - dummyEvent = { - preventDefault: function() { this.defaultPrevented = true; }, - isDefaultPrevented: function() { return this.defaultPrevented === true; }, - stopImmediatePropagation: function() { this.immediatePropagationStopped = true; }, - isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; }, - stopPropagation: noop, - type: eventName, - target: element - }; + // 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); + 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); + } - // If a custom event was provided then extend our dummy event with it - if (event.type) { - dummyEvent = extend(dummyEvent, event); - } + // 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); + } + } - // Copy event handlers in case event handlers array is modified during execution. - eventFnsCopy = shallowCopy(eventFns); - handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; + return result; + }; - forEach(eventFnsCopy, function(fn) { - if (!dummyEvent.isImmediatePropagationStopped()) { - fn.apply(element, handlerArgs); - } - }); - } - } -}, function(fn, name) { - /** - * chaining functions - */ - JQLite.prototype[name] = function(arg1, arg2, arg3) { - var value; - - for (var i = 0, ii = this.length; i < ii; i++) { - if (isUndefined(value)) { - value = fn(this[i], arg1, arg2, arg3); - if (isDefined(value)) { - // any function which returns a value needs to be wrapped - value = jqLite(value); - } - } else { - jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); - } - } - return isDefined(value) ? value : this; - }; - - // bind legacy bind/unbind to on/off - JQLite.prototype.bind = JQLite.prototype.on; - JQLite.prototype.unbind = JQLite.prototype.off; -}); - - -// Provider for private $$jqLite service -function $$jqLiteProvider() { - this.$get = function $$jqLite() { - return extend(JQLite, { - hasClass: function(node, classes) { - if (node.attr) node = node[0]; - return jqLiteHasClass(node, classes); - }, - addClass: function(node, classes) { - if (node.attr) node = node[0]; - return jqLiteAddClass(node, classes); - }, - removeClass: function(node, classes) { - if (node.attr) node = node[0]; - return jqLiteRemoveClass(node, classes); - } - }); - }; -} - -/** - * Computes a hash of an 'obj'. - * Hash of a: - * string is string - * number is number as string - * object is either result of calling $$hashKey function on the object or uniquely generated id, - * that is also assigned to the $$hashKey property of the object. - * - * @param obj - * @returns {string} hash string such that the same input will have the same hash string. - * The resulting string key is in 'type:hashKey' format. - */ -function hashKey(obj, nextUidFn) { - var key = obj && obj.$$hashKey; - - if (key) { - if (typeof key === 'function') { - key = obj.$$hashKey(); - } - return key; - } - - var objType = typeof obj; - if (objType == 'function' || (objType == 'object' && obj !== null)) { - key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)(); - } else { - key = objType + ':' + obj; - } - - return key; -} - -/** - * HashMap which can use objects as keys - */ -function HashMap(array, isolatedUid) { - if (isolatedUid) { - var uid = 0; - this.nextUid = function() { - return ++uid; - }; - } - forEach(array, this.put, this); -} -HashMap.prototype = { - /** - * Store key value pair - * @param key key to store can be any type - * @param value value to store can be any type - */ - put: function(key, value) { - this[hashKey(key, this.nextUid)] = value; - }, - - /** - * @param key - * @returns {Object} the value for the key - */ - get: function(key) { - return this[hashKey(key, this.nextUid)]; - }, - - /** - * Remove the key/value pair - * @param key - */ - remove: function(key) { - var value = this[key = hashKey(key, this.nextUid)]; - delete this[key]; - return value; - } -}; - -var $$HashMapProvider = [function() { - this.$get = [function() { - return HashMap; - }]; -}]; - -/** - * @ngdoc function - * @module ng - * @name angular.injector - * @kind function - * - * @description - * Creates an injector object that can be used for retrieving services as well as for - * dependency injection (see {@link guide/di dependency injection}). - * - * @param {Array.
TRANSLATION_ID+ + +
{{translationId}}+ +
WITH_VALUES+ +
WITH_VALUES+ + + +
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'); + }); + }); +Cached Values
-Cache Info
-{{ 'TRANSLATION_ID' | translate }}+
{{ translationId | translate }}+
{{ 'WITH_VALUES' | translate:'{value: 5}' }}+
{{ 'WITH_VALUES' | translate:values }}+ +
{{ $ctrl.log | json }}', - * controller: function() { - * var previousValue; - * this.log = []; - * this.$doCheck = function() { - * var currentValue = this.date && this.date.valueOf(); - * if (previousValue !== currentValue) { - * this.log.push('doCheck: date mutated: ' + this.date); - * previousValue = currentValue; - * } - * }; - * } - * }); - *
{{ items }}- *
{{ $ctrl.log | json }}', - * controller: function() { - * this.log = []; - * - * this.$doCheck = function() { - * if (this.items_ref !== this.items) { - * this.log.push('doCheck: items changed'); - * this.items_ref = this.items; - * } - * if (!angular.equals(this.items_clone, this.items)) { - * this.log.push('doCheck: items mutated'); - * this.items_clone = angular.copy(this.items); - * } - * }; - * } - * }); - *