diff options
-rw-r--r-- | css/public/style.css | 41 | ||||
-rw-r--r-- | js/components/contact/contact_controller.js | 4 | ||||
-rw-r--r-- | js/components/contactDetails/contactDetails_controller.js | 3 | ||||
-rw-r--r-- | js/models/contact_model.js | 35 | ||||
-rw-r--r-- | js/tests/models/contact_model.js | 7 | ||||
-rw-r--r-- | templates/contact.html | 1 | ||||
-rw-r--r-- | templates/contactDetails.html | 5 |
7 files changed, 85 insertions, 11 deletions
diff --git a/css/public/style.css b/css/public/style.css index dd038bd0..4d54aae2 100644 --- a/css/public/style.css +++ b/css/public/style.css @@ -71,18 +71,18 @@ opacity: .8; } -.contactdetails__header #details-actions .icon-delete-white, -.contactdetails__header #details-actions #contact-export-link { +.contactdetails__header #details-actions a, +.contactdetails__header #details-actions button { padding: 15px; background-color: transparent; border: none; opacity: .5; margin: 3px; } -.contactdetails__header #details-actions .icon-delete-white:hover, -.contactdetails__header .icon-delete-white:focus, -.contactdetails__header #details-actions #contact-export-link:hover, -.contactdetails__header #details-actions #contact-export-link:focus { +.contactdetails__header #details-actions a:hover, +.contactdetails__header #details-actions button:hover, +.contactdetails__header #details-actions a:focus, +.contactdetails__header #details-actions button:focus { opacity: .7 } @@ -284,6 +284,11 @@ detailsitem.details-item-note label { vertical-align: top; } +detailsitem.failed > label, +detailsitem.failed > select { + border-left: 2px solid red; +} + /* Prevent delete for last adr/mail/tel item */ .last-details > detailsitem.details-item-adr .icon-delete, .last-details > detailsitem.details-item-email .icon-delete, @@ -396,6 +401,28 @@ li.addressBook-share-item span.shareeIdentifier { background-position: 10px center; } +contactlist .tooltip { + max-width: 75%; +} + +.app-content-list-item-failed { + position: absolute; + right: 15px; + top: 50%; + margin-top: -15px; + opacity: 0.2; + width: 30px; + height: 30px; + z-index: 50; +} +.app-content-list-item-failed:hover { + opacity: 0.5; +} +.app-content-list-item-failed ~ .app-content-list-item-line-one, +.app-content-list-item-failed ~ .app-content-list-item-line-two { + padding-right: 50px; +} + /* app content list & detail view */ @@ -593,4 +620,4 @@ detailsitem .select2-container { } #app-navigation-toggle-back { display: none; -}
\ No newline at end of file +} diff --git a/js/components/contact/contact_controller.js b/js/components/contact/contact_controller.js index 5c4d7311..2d84076f 100644 --- a/js/components/contact/contact_controller.js +++ b/js/components/contact/contact_controller.js @@ -2,6 +2,10 @@ angular.module('contactsApp') .controller('contactCtrl', function($route, $routeParams) { var ctrl = this; + ctrl.t = { + errorMessage : t('contacts', 'This card is corrupted and has been fixed. Please check the data and trigger a save to make the changes permanent.'), + }; + ctrl.openContact = function() { $route.updateParams({ gid: $routeParams.gid, diff --git a/js/components/contactDetails/contactDetails_controller.js b/js/components/contactDetails/contactDetails_controller.js index 48705e43..46b4f6e3 100644 --- a/js/components/contactDetails/contactDetails_controller.js +++ b/js/components/contactDetails/contactDetails_controller.js @@ -23,7 +23,8 @@ angular.module('contactsApp') placeholderTitle : t('contacts', 'Title'), selectField : t('contacts', 'Add field ...'), download : t('contacts', 'Download'), - delete : t('contacts', 'Delete') + delete : t('contacts', 'Delete'), + save : t('contacts', 'Save changes') }; ctrl.fieldDefinitions = vCardPropertiesService.fieldDefinitions; diff --git a/js/models/contact_model.js b/js/models/contact_model.js index 02e1c2f3..ca31cb39 100644 --- a/js/models/contact_model.js +++ b/js/models/contact_model.js @@ -5,6 +5,7 @@ angular.module('contactsApp') data: {}, props: {}, + failedProps: [], dateProperties: ['bday', 'anniversary', 'deathdate'], @@ -155,7 +156,7 @@ angular.module('contactsApp') return this.setProperty('categories', { value: value }); } else { // getter - var property = this.getProperty('categories'); + var property = this.validate('categories', this.getProperty('categories')); if(!property) { return []; } @@ -267,6 +268,16 @@ angular.module('contactsApp') // keep vCard in sync self.data.addressData = $filter('JSON2vCard')(self.props); + + // Revalidate all props + _.each(self.failedProps, function(name, index) { + if (!_.isUndefined(self.props[name]) && !_.isUndefined(self.props[name][0])) { + // Set dates again to make sure they are in RFC-6350 format + self.failedProps.splice(index, 1); + self.validate(name, self.props[name][0]); + } + }); + }, matches: function(pattern) { @@ -294,6 +305,26 @@ angular.module('contactsApp') return false; }); return matchingProps.length > 0; + }, + + validate: function(prop, property) { + switch(prop) { + case 'categories': + // Avoid unescaped commas + if (angular.isArray(property.value)) { + if(property.value.join(';').indexOf(',') !== -1) { + this.failedProps.push(prop); + property.value = property.value.join(',').split(','); + } + } else if (angular.isString(property.value)) { + if(property.value.indexOf(',') !== -1) { + this.failedProps.push(prop); + property.value = property.value.split(','); + } + } + break; + } + return property; } }); @@ -311,7 +342,7 @@ angular.module('contactsApp') var property = this.getProperty('categories'); if(!property) { - this.categories(''); + this.categories([]); } else { if (angular.isString(property.value)) { this.categories([property.value]); diff --git a/js/tests/models/contact_model.js b/js/tests/models/contact_model.js index 09fbb2d6..c8165287 100644 --- a/js/tests/models/contact_model.js +++ b/js/tests/models/contact_model.js @@ -41,4 +41,11 @@ describe('contactModel', function() { var d = contact.getISODate(new Date('2016-09-01T09:07:05Z')); expect(d).to.equal('20160901T090705Z'); }); + + it('should fix invalid group array', function() { + var contact = new $Contact({displayName: 'test'}); + contact.categories(['Test 1', 'Test 2\,Test 3']); + var categories = contact.categories(); + expect(categories).to.deep.equal(['Test 1', 'Test 2', 'Test 3']); + }); }); diff --git a/templates/contact.html b/templates/contact.html index 48bb74ed..b6e68c00 100644 --- a/templates/contact.html +++ b/templates/contact.html @@ -2,6 +2,7 @@ <img class="app-content-list-item-icon contact__icon" ng-show="ctrl.contact.photo()!==undefined" data-ng-src="{{ctrl.contact.photo()}}" /> <div class="app-content-list-item-icon contact__icon" ng-show="ctrl.contact.photo()===undefined" ng-style="{'background-color': (ctrl.contact.uid() | contactColor) }">{{ ctrl.contact.displayName() | firstCharacter }}</div> <div class="app-content-list-item-star icon-star" data-starred="false"></div> + <div class="app-content-list-item-failed icon-error" tooltip-placement="auto left" ng-if="ctrl.contact.failedProps.length>0" uib-tooltip="{{ ctrl.t.errorMessage }}"></div> <div class="app-content-list-item-line-one" ng-class="{'no-line-two':!ctrl.contact.email()}">{{ ctrl.contact.displayName() | newContact }}</div> <div class="app-content-list-item-line-two">{{ctrl.contact.email()}}</div> </a> diff --git a/templates/contactDetails.html b/templates/contactDetails.html index db75f686..07eea2de 100644 --- a/templates/contactDetails.html +++ b/templates/contactDetails.html @@ -24,6 +24,7 @@ </div> </div> <div id="details-actions"> + <button id="contact-failed-save" ng-click="ctrl.updateContact()" class="icon-checkmark-white" ng-if="ctrl.contact.failedProps.length>0" title="{{ctrl.t.save}}"></button> <a href="{{ctrl.contact.data.url}}" id="contact-export-link" class="icon-download-white" title="{{ctrl.t.download}}" download="{{ ctrl.contact.readableFilename() }}"></a> @@ -32,7 +33,8 @@ </header> <section> <div ng-repeat="prop in ctrl.contact.props | toArray | orderDetailItems:'$key'"> - <detailsItem ng-repeat="propData in prop" name="prop.$key" data="propData" model="ctrl" index="$index" ng-class="[ 'details-item-' + prop.$key ]"></detailsItem> + <detailsItem ng-repeat="propData in prop" name="prop.$key" data="propData" model="ctrl" index="$index" + class="details-item-{{prop.$key}}" ng-class="{ 'failed': ctrl.contact.failedProps.indexOf(prop.$key) !== -1 }"></detailsItem> </div> <div class="select-addressbook" ng-if="ctrl.addressBooks.length > 1"> <select ng-model="ctrl.addressBook" ng-change="ctrl.changeAddressBook(ctrl.addressBook)" ng-options="book.displayName for book in ctrl.addressBooks"> @@ -45,3 +47,4 @@ </section> </div> </div> + |