'use strict'; function selectText(element) { var doc = document; if (doc.body.createTextRange) { // ms var range = doc.body.createTextRange(); range.moveToElementText(element); range.select(); } else if (window.getSelection) { var selection = window.getSelection(); var range = doc.createRange(); range.selectNodeContents(element); selection.removeAllRanges(); selection.addRange(range); } } angular.module('copayApp.directives') .directive('validAddress', ['$rootScope', 'bitcore', 'profileService', function($rootScope, bitcore, profileService) { return { require: 'ngModel', link: function(scope, elem, attrs, ctrl) { var URI = bitcore.URI; var Address = bitcore.Address var validator = function(value) { if (!profileService.focusedClient) return; var networkName = profileService.focusedClient.credentials.network; // Regular url if (/^https?:\/\//.test(value)) { ctrl.$setValidity('validAddress', true); return value; } // Bip21 uri if (/^bitcoin:/.test(value)) { var uri, isAddressValid; var isUriValid = URI.isValid(value); if (isUriValid) { uri = new URI(value); isAddressValid = Address.isValid(uri.address.toString(), networkName) } ctrl.$setValidity('validAddress', isUriValid && isAddressValid); return value; } if (typeof value == 'undefined') { ctrl.$pristine = true; return; } // Regular Address ctrl.$setValidity('validAddress', Address.isValid(value, networkName)); return value; }; ctrl.$parsers.unshift(validator); ctrl.$formatters.unshift(validator); } }; } ]) .directive('validUrl', [ function() { return { require: 'ngModel', link: function(scope, elem, attrs, ctrl) { var validator = function(value) { // Regular url if (/^https?:\/\//.test(value)) { ctrl.$setValidity('validUrl', true); return value; } else { ctrl.$setValidity('validUrl', false); return value; } }; ctrl.$parsers.unshift(validator); ctrl.$formatters.unshift(validator); } }; } ]) .directive('validAmount', ['configService', '$locale', function(configService, locale) { var formats = locale.NUMBER_FORMATS; return { require: 'ngModel', link: function(scope, element, attrs, ctrl) { var val = function(value) { var settings = configService.getSync().wallet.settings; var vNum = Number((value * settings.unitToSatoshi).toFixed(0)); if (typeof value == 'undefined') { ctrl.$pristine = true; } if (typeof vNum == "number" && vNum > 0) { var decimals = Number(settings.unitDecimals); var sep_index = ('' + value).indexOf(formats.DECIMAL_SEP); var str_value = ('' + value).substring(sep_index + 1); if (sep_index > 0 && str_value.length > decimals) { ctrl.$setValidity('validAmount', false); } else { ctrl.$setValidity('validAmount', true); } } else { ctrl.$setValidity('validAmount', false); } return value; } ctrl.$parsers.unshift(val); ctrl.$formatters.unshift(val); } }; } ]) .directive('walletSecret', function(bitcore) { return { require: 'ngModel', link: function(scope, elem, attrs, ctrl) { var validator = function(value) { if (value.length > 0) { var m = value.match(/^[0-9A-HJ-NP-Za-km-z]{70,80}$/); ctrl.$setValidity('walletSecret', m ? true : false); } return value; }; ctrl.$parsers.unshift(validator); } }; }) .directive('loading', function() { return { restrict: 'A', link: function($scope, element, attr) { var a = element.html(); var text = attr.loading; element.on('click', function() { element.html(' ' + text + '...'); }); $scope.$watch('loading', function(val) { if (!val) { element.html(a); } }); } } }) .directive('ngFileSelect', function() { return { link: function($scope, el) { el.bind('change', function(e) { $scope.file = (e.srcElement || e.target).files[0]; $scope.getFile(); }); } } }) .directive('contact', function() { return { restrict: 'E', link: function(scope, element, attrs) { if (!scope.wallet) return; var address = attrs.address; var contact = scope.wallet.addressBook[address]; if (contact && !contact.hidden) { element.append(contact.label); element.attr('tooltip', attrs.address); } else { element.append(address); } element.bind('click', function() { selectText(element[0]); }); } }; }) .directive('highlightOnChange', function() { return { restrict: 'A', link: function(scope, element, attrs) { scope.$watch(attrs.highlightOnChange, function(newValue, oldValue) { element.addClass('highlight'); setTimeout(function() { element.removeClass('highlight'); }, 500); }); } } }) .directive('checkStrength', function() { return { replace: false, restrict: 'EACM', require: 'ngModel', link: function(scope, element, attrs) { var MIN_LENGTH = 8; var MESSAGES = ['Very Weak', 'Very Weak', 'Weak', 'Medium', 'Strong', 'Very Strong']; var COLOR = ['#dd514c', '#dd514c', '#faa732', '#faa732', '#16A085', '#16A085']; function evaluateMeter(password) { var passwordStrength = 0; var text; if (password.length > 0) passwordStrength = 1; if (password.length >= MIN_LENGTH) { if ((password.match(/[a-z]/)) && (password.match(/[A-Z]/))) { passwordStrength++; } else { text = ', add mixed case'; } if (password.match(/\d+/)) { passwordStrength++; } else { if (!text) text = ', add numerals'; } if (password.match(/.[!,@,#,$,%,^,&,*,?,_,~,-,(,)]/)) { passwordStrength++; } else { if (!text) text = ', add punctuation'; } if (password.length > 12) { passwordStrength++; } else { if (!text) text = ', add characters'; } } else { text = ', that\'s short'; } if (!text) text = ''; return { strength: passwordStrength, message: MESSAGES[passwordStrength] + text, color: COLOR[passwordStrength] } } scope.$watch(attrs.ngModel, function(newValue, oldValue) { if (newValue && newValue !== '') { var info = evaluateMeter(newValue); scope[attrs.checkStrength] = info; } }); } }; }) .directive('showFocus', function($timeout) { return function(scope, element, attrs) { scope.$watch(attrs.showFocus, function(newValue) { $timeout(function() { newValue && element[0].focus(); }); }, true); }; }) .directive('match', function() { return { require: 'ngModel', restrict: 'A', scope: { match: '=' }, link: function(scope, elem, attrs, ctrl) { scope.$watch(function() { return (ctrl.$pristine && angular.isUndefined(ctrl.$modelValue)) || scope.match === ctrl.$modelValue; }, function(currentValue) { ctrl.$setValidity('match', currentValue); }); } }; }) .directive('clipCopy', function() { return { restrict: 'A', scope: { clipCopy: '=clipCopy' }, link: function(scope, elm) { // TODO this does not work (FIXME) elm.attr('tooltip', 'Press Ctrl+C to Copy'); elm.attr('tooltip-placement', 'top'); elm.bind('click', function() { selectText(elm[0]); }); } }; });