summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--css/public/style.css41
-rw-r--r--js/components/contact/contact_controller.js4
-rw-r--r--js/components/contactDetails/contactDetails_controller.js3
-rw-r--r--js/models/contact_model.js35
-rw-r--r--js/tests/models/contact_model.js7
-rw-r--r--templates/contact.html1
-rw-r--r--templates/contactDetails.html5
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>
+