diff --git a/src/popup/app/app.js b/src/popup/app/app.js index ea4f9b95..7217c528 100644 --- a/src/popup/app/app.js +++ b/src/popup/app/app.js @@ -4,6 +4,7 @@ 'angular-jwt', 'ngAnimate', + 'bit.directives', 'bit.services', 'bit.global', diff --git a/src/popup/app/directives/directivesModule.js b/src/popup/app/directives/directivesModule.js new file mode 100644 index 00000000..ca425097 --- /dev/null +++ b/src/popup/app/directives/directivesModule.js @@ -0,0 +1,2 @@ +angular + .module('bit.directives', []); diff --git a/src/popup/app/directives/fieldDirective.js b/src/popup/app/directives/fieldDirective.js new file mode 100644 index 00000000..b9066666 --- /dev/null +++ b/src/popup/app/directives/fieldDirective.js @@ -0,0 +1,30 @@ +angular + .module('bit.directives') + + .directive('bitField', function () { + var linkFn = function (scope, element, attrs, ngModel) { + ngModel.$registerError = registerError; + ngModel.$validators.validate = validator; + + function validator() { + ngModel.$setValidity('bit', true); + return true; + } + + function registerError() { + ngModel.$setValidity('bit', false); + } + }; + + return { + require: 'ngModel', + restrict: 'A', + compile: function (elem, attrs) { + if (!attrs.name || attrs.name === '') { + throw 'bit-field element does not have a valid name attribute'; + } + + return linkFn; + } + }; + }); diff --git a/src/popup/app/directives/formDirective.js b/src/popup/app/directives/formDirective.js new file mode 100644 index 00000000..cefdf1b6 --- /dev/null +++ b/src/popup/app/directives/formDirective.js @@ -0,0 +1,35 @@ +angular + .module('bit.directives') + + .directive('bitForm', function ($rootScope, validationService) { + return { + require: 'form', + restrict: 'A', + link: function (scope, element, attrs, formCtrl) { + var watchPromise = attrs.bitForm || null; + if (watchPromise) { + scope.$watch(watchPromise, formSubmitted.bind(null, formCtrl, scope)); + } + } + }; + + function formSubmitted(form, scope, promise) { + if (!promise || !promise.then) { + return; + } + + // reset errors + form.$errors = null; + + // start loading + form.$loading = true; + + promise.then(function success(response) { + form.$loading = false; + }, function failure(reason) { + form.$loading = false; + validationService.addErrors(form, reason); + scope.$broadcast('show-errors-check-validity'); + }); + } + }); diff --git a/src/popup/app/services/cipherService.js b/src/popup/app/services/cipherService.js index f06aacfc..3ae595c3 100644 --- a/src/popup/app/services/cipherService.js +++ b/src/popup/app/services/cipherService.js @@ -4,23 +4,25 @@ .factory('cipherService', function (cryptoService, $q) { var _service = {}; - _service.encryptSite = function (site, callback) { + _service.encryptSite = function (site) { var model = {}; - cryptoService.encrypt(site.name, function (nameCipherString) { - model.name = nameCipherString; - cryptoService.encrypt(site.uri, function (uriCipherString) { - model.uri = uriCipherString; - cryptoService.encrypt(site.username, function (usernameCipherString) { - model.username = usernameCipherString; - cryptoService.encrypt(site.password, function (passwordCipherString) { - model.password = passwordCipherString; - cryptoService.encrypt(site.notes, function (notesCipherString) { - model.notes = notesCipherString; - callback(model); - }); - }); - }); + return $q(function (resolve, reject) { + encrypt(site.name).then(function (cs) { + model.name = cs; + return encrypt(site.uri); + }).then(function (cs) { + model.uri = cs; + return encrypt(site.username); + }).then(function (cs) { + model.username = cs; + return encrypt(site.password); + }).then(function (cs) { + model.password = cs; + return encrypt(site.notes); + }).then(function (cs) { + model.notes = cs; + resolve(model); }); }); }; @@ -73,5 +75,13 @@ }); } + function encrypt(plaintextString) { + return $q(function (resolve, reject) { + cryptoService.encrypt(plaintextString, function (cipherString) { + resolve(cipherString); + }); + }); + } + return _service; }); diff --git a/src/popup/app/services/validationService.js b/src/popup/app/services/validationService.js new file mode 100644 index 00000000..1af09d10 --- /dev/null +++ b/src/popup/app/services/validationService.js @@ -0,0 +1,62 @@ +angular + .module('bit.services') + + .factory('validationService', function () { + var _service = {}; + + _service.addErrors = function (form, reason) { + var data = reason.data; + var defaultErrorMessage = 'An unexpected error has occured.'; + form.$errors = []; + + if (!data || !angular.isObject(data)) { + form.$errors.push(defaultErrorMessage); + return; + } + + if (!data.validationErrors) { + if (data.message) { + form.$errors.push(data.message); + } + else { + form.$errors.push(defaultErrorMessage); + } + + return; + } + + for (var key in data.validationErrors) { + if (!data.validationErrors.hasOwnProperty(key)) { + continue; + } + + for (var i = 0; i < data.validationErrors[key].length; i++) { + _service.addError(form, key, data.validationErrors[key][i]); + } + } + }; + + _service.addError = function (form, key, errorMessage, clearExistingErrors) { + if (clearExistingErrors || !form.$errors) { + form.$errors = []; + } + + var pushError = true; + for (var i = 0; i < form.$errors.length; i++) { + if (form.$errors[i] === errorMessage) { + pushError = false; + break; + } + } + + if (pushError) { + form.$errors.push(errorMessage); + } + + if (key && key !== '' && form[key] && form[key].$registerError) { + form[key].$registerError(); + } + }; + + return _service; + }); diff --git a/src/popup/app/vault/vaultAddSiteController.js b/src/popup/app/vault/vaultAddSiteController.js index 78bb46fc..daf061e0 100644 --- a/src/popup/app/vault/vaultAddSiteController.js +++ b/src/popup/app/vault/vaultAddSiteController.js @@ -1,16 +1,29 @@ angular .module('bit.vault') - .controller('vaultAddSiteController', function ($scope, $state, siteService, cipherService) { + .controller('vaultAddSiteController', function ($scope, $state, siteService, cipherService, $q) { $scope.site = { folderId: null }; + $('#name').focus(); + $('.list-section-item').click(function (e) { + e.preventDefault(); + $(this).find('input[type="text"], textarea, select').focus(); + var checkbox = $(this).find('input[type="checkbox"]'); + if (checkbox.length > 0) { + checkbox.prop('checked', !checkbox.is(':checked')); + } + }); + + $scope.savePromise = null; $scope.save = function (model) { - cipherService.encryptSite(model, function (siteModel) { + $scope.savePromise = cipherService.encryptSite(model).then(function (siteModel) { var site = new Site(siteModel, true); - siteService.saveWithServer(site, function () { - $scope.close(); + return site; + }).then(function (site) { + return saveSite(site, function (site) { + alert('Saved ' + site.id + '!'); }); }); }; @@ -18,4 +31,14 @@ $scope.close = function () { $state.go('tabs.vault', { animation: 'out-slide-down' }); }; + + function saveSite(site) { + return $q(function (resolve, reject) { + siteService.saveWithServer(site, function (site) { + resolve(site); + }, function (error) { + reject(error); + }); + }); + } }); diff --git a/src/popup/app/vault/views/vaultAddSite.html b/src/popup/app/vault/views/vaultAddSite.html index 2a971b3a..acd0c055 100644 --- a/src/popup/app/vault/views/vaultAddSite.html +++ b/src/popup/app/vault/views/vaultAddSite.html @@ -1,10 +1,11 @@ -