diff --git a/UI/Templates/ContactsUI/UIxContactFoldersView.wox b/UI/Templates/ContactsUI/UIxContactFoldersView.wox
index 39a5757a4..4a76231d5 100644
--- a/UI/Templates/ContactsUI/UIxContactFoldersView.wox
+++ b/UI/Templates/ContactsUI/UIxContactFoldersView.wox
@@ -92,26 +92,41 @@
@@ -169,20 +184,21 @@
-
@@ -203,7 +219,7 @@
-
+
diff --git a/UI/WebServerResources/js/Common/resource.js b/UI/WebServerResources/js/Common/resource.js
index 8f6964497..5c6288dda 100644
--- a/UI/WebServerResources/js/Common/resource.js
+++ b/UI/WebServerResources/js/Common/resource.js
@@ -2,18 +2,19 @@
'use strict';
/* Constructor */
- function Resource($http, $q, path) {
+ function Resource($http, $q, path, options) {
angular.extend(this, {
_http: $http,
_q: $q,
_path: path
});
+ angular.extend(this, options);
}
/* The factory we'll use to register with Angular */
Resource.$factory = ['$http', '$q', function($http, $q) {
- return function(path) {
- return new Resource($http, $q, path);
+ return function(path, options) {
+ return new Resource($http, $q, path, options);
};
}];
@@ -29,7 +30,8 @@
Resource.prototype.find = function(uid) {
var deferred = this._q.defer();
- this._http.get(this.path(uid))
+ this._http
+ .get(this.path(uid))
.success(deferred.resolve)
.error(deferred.reject);
@@ -50,9 +52,22 @@
return deferred.promise;
};
- Resource.prototype.set = function(uid, newValue) {
+ Resource.prototype.newguid = function(uid) {
var deferred = this._q.defer();
- var path = this._path + '/' + uid + '/save';
+ var path = this._path + '/' + uid + '/newguid';
+
+ this._http
+ .get(path)
+ .success(deferred.resolve)
+ .error(deferred.reject);
+
+ return deferred.promise;
+ };
+
+ Resource.prototype.set = function(uid, newValue, options) {
+ var deferred = this._q.defer();
+ var action = (options && options.action)? options.action : 'save';
+ var path = this._path + '/' + uid + '/' + action;
this._http
.post(path, newValue)
@@ -62,4 +77,15 @@
return deferred.promise;
};
+ Resource.prototype.remove = function(uid) {
+ var deferred = this._q.defer();
+ var path = this._path + '/' + uid + '/delete';
+
+ this._http
+ .get(path)
+ .success(deferred.resolve)
+ .error(deferred.reject);
+
+ return deferred.promise;
+ };
})();
diff --git a/UI/WebServerResources/js/Contacts/addressbook-model.js b/UI/WebServerResources/js/Contacts/addressbook-model.js
index ebcccd659..6b3d83824 100644
--- a/UI/WebServerResources/js/Contacts/addressbook-model.js
+++ b/UI/WebServerResources/js/Contacts/addressbook-model.js
@@ -27,6 +27,7 @@
/* Factory registration in Angular module */
angular.module('SOGo.Contacts').factory('sgAddressBook', AddressBook.$factory);
+ /* Set or get the list of addressbooks */
AddressBook.$all = function(data) {
if (data) {
this.$addressbooks = data;
@@ -34,10 +35,10 @@
return this.$addressbooks;
};
- /* Fetch list of cards */
+ /* Fetch list of cards and return an AddressBook instance */
AddressBook.$find = function(addressbook_id) {
- var futureAddressBookData = AddressBook.$$resource.find(addressbook_id); // should return attributes of addressbook +
- // AddressBook.$selectedId(addressbook_id);
+ var futureAddressBookData = AddressBook.$$resource.find(addressbook_id);
+
return new AddressBook(futureAddressBookData);
};
@@ -59,32 +60,35 @@
return this.$id().then(function(addressbook_id) {
var futureAddressBookData = AddressBook.$$resource.filter(addressbook_id, params);
return futureAddressBookData.then(function(data) {
- return AddressBook.$timeout(function() {
- self.cards = data.cards;
- return self.cards;
+ self.cards = data.cards;
+ // Instanciate Card objects
+ angular.forEach(self.cards, function(o, i) {
+ self.cards[i] = new AddressBook.$Card(o);
});
+ return self.cards;
});
});
};
+ AddressBook.prototype.$delete = function() {
+ return AddressBook.$$resource.remove(this.id);
+ };
+
+ AddressBook.prototype.$save = function() {
+ return AddressBook.$$resource.set(this.id, this.$omit()).then(function (data) {
+ return data;
+ });
+ };
+
AddressBook.prototype.$getCard = function(card_id) {
var self = this;
return this.$id().then(function(addressbook_id) {
- var card = AddressBook.$Card.$find(addressbook_id, card_id);
-
- AddressBook.$timeout(function() {
- self.card = card;
- });
-
- return card.$futureCardData.then(function() {
- angular.forEach(self.cards, function(o, i) {
- if (o.c_name == card_id) {
- angular.extend(self.card, o);
- }
- });
-
- return self.card;
+ self.card = AddressBook.$Card.$find(addressbook_id, card_id);
+ self.card.$id().catch(function() {
+ // Card not found
+ self.card = null;
});
+ return self.card;
});
};
@@ -93,7 +97,7 @@
};
AddressBook.prototype.$viewerIsActive = function(editMode) {
- return (!angular.isUndefined(this.card) && !editMode);
+ return (this.card != null && !editMode);
};
// Unwrap a promise
@@ -104,12 +108,16 @@
this.$futureAddressBookData.then(function(data) {
AddressBook.$timeout(function() {
angular.extend(self, data);
- self.$id().then(function(addressbook_id) {
- angular.forEach(AddressBook.$all(), function(o, i) {
- if (o.id == addressbook_id) {
- angular.extend(self, o);
- }
- });
+ // Also extend AddressBook instance from data of addressbooks list.
+ // Will inherit attributes such as isEditable and isRemote.
+ angular.forEach(AddressBook.$all(), function(o, i) {
+ if (o.id == self.id) {
+ angular.extend(self, o);
+ }
+ });
+ // Instanciate Card objects
+ angular.forEach(self.cards, function(o, i) {
+ self.cards[i] = new AddressBook.$Card(o);
});
});
});
@@ -128,10 +136,4 @@
});
return addressbook;
};
-
- AddressBook.prototype.$save = function() {
- return AddressBook.$$resource.set(this.id, this.$omit()).then(function (data) {
- return data;
- });
- };
})();
diff --git a/UI/WebServerResources/js/Contacts/card-model.js b/UI/WebServerResources/js/Contacts/card-model.js
index 56a7b6659..12f4e5855 100644
--- a/UI/WebServerResources/js/Contacts/card-model.js
+++ b/UI/WebServerResources/js/Contacts/card-model.js
@@ -2,16 +2,21 @@
'use strict';
/* Constructor */
-
function Card(futureCardData) {
- /* DATA IS IMMEDIATELY AVAILABLE */
- // if (!futureCardData.inspect) {
- // angular.extend(this, futureCardData);
- // return;
- // }
+ // Data is immediately available
+ if (typeof futureCardData.then !== 'function') {
+ angular.extend(this, futureCardData);
+ if (this.pid && !this.id) {
+ // Prepare for the creation of a new card;
+ // Get UID from the server.
+ var newCardData = Card.$$resource.newguid(this.pid);
+ this.$unwrap(newCardData);
+ }
+ return;
+ }
- /* THE PROMISE WILL BE UNWRAPPED FIRST */
+ // The promise will be unwrapped first
this.$unwrap(futureCardData);
}
@@ -20,7 +25,7 @@
Card.$url_types = ['work', 'home', 'pref'];
Card.$address_types = ['work', 'home'];
- /* THE FACTORY WE'LL USE TO REGISTER WITH ANGULAR */
+ /* The factory we'll use to register with Angular */
Card.$factory = ['$timeout', 'sgSettings', 'sgResource', function($timeout, Settings, Resource) {
angular.extend(Card, {
$$resource: new Resource(Settings.baseURL),
@@ -30,10 +35,11 @@
return Card; // return constructor
}];
- /* ANGULAR MODULE REGISTRATION */
+ /* Factory registration in Angular module */
angular.module('SOGo.Contacts')
.factory('sgCard', Card.$factory)
+ // Directive to format a postal address
.directive('sgAddress', function() {
return {
restrict: 'A',
@@ -57,12 +63,8 @@
}
});
-
- /* FETCH A UNIT */
+ /* Fetch a card */
Card.$find = function(addressbook_id, card_id) {
-
- /* FALLS BACK ON AN INSTANCE OF RESOURCE */
- // Fetch data over the wire.
var futureCardData = this.$$resource.find([addressbook_id, card_id].join('/'));
if (card_id) return new Card(futureCardData); // a single card
@@ -70,26 +72,53 @@
return Card.$unwrapCollection(futureCardData); // a collection of cards
};
- // Instance methods
+ /* Unwrap to a collection of Card instances */
+ Card.$unwrapCollection = function(futureCardData) {
+ var collection = {};
+
+ collection.$futureCardData = futureCardData;
+
+ futureCardData.then(function(cards) {
+ Card.$timeout(function() {
+ angular.forEach(cards, function(data, index) {
+ collection[data.id] = new Card(data);
+ });
+ });
+ });
+
+ return collection;
+ };
+
+ /* Instance methods */
Card.prototype.$id = function() {
- //var self = this;
- return this.$futureAddressBookData.then(function(data) {
+ return this.$futureCardData.then(function(data) {
return data.id;
});
};
- // Unwrap a promise
- Card.prototype.$unwrap = function(futureCardData) {
- var self = this;
-
- this.$futureCardData = futureCardData;
- this.$futureCardData.then(function(data) {
- // The success callback. Calling _.extend from $timeout will wrap it into a try/catch call.
- Card.$timeout(function() {
- angular.extend(self, data);
+ Card.prototype.$save = function() {
+ var action = 'saveAsContact';
+ if (this.tag == 'VLIST') action = 'saveAsList';
+ //var action = 'saveAs' + this.tag.substring(1).capitalize();
+ return Card.$$resource.set([this.pid, this.id || '_new_'].join('/'),
+ this.$omit(),
+ { 'action': action })
+ .then(function (data) {
+ return data;
});
- });
+ };
+
+ Card.prototype.$delete = function(attribute, index) {
+ if (attribute) {
+ if (index > -1 && this[attribute].length > index) {
+ this[attribute].splice(index, 1);
+ }
+ }
+ else {
+ // No arguments -- delete card
+ return Card.$$resource.remove([this.pid, this.id].join('/'));
+ }
};
Card.prototype.$fullname = function() {
@@ -110,6 +139,9 @@
else if (this.emails && this.emails.length > 0) {
fn = _.find(this.emails, function(i) { return i.value != ''; }).value;
}
+ else if (this.c_cn && this.c_cn.length > 0) {
+ fn = this.c_cn;
+ }
}
return fn;
@@ -130,22 +162,30 @@
return description.join(', ');
};
+ Card.prototype.$preferredEmail = function() {
+ var email = _.find(this.emails, function(o) {
+ return o.type == 'pref';
+ });
+ if (email) {
+ email = email.value;
+ }
+ else if (this.emails && this.emails.length) {
+ email = this.emails[0].value;
+ }
+
+ return email;
+ };
+
Card.prototype.$birthday = function() {
return new Date(this.birthday*1000);
};
Card.prototype.$isCard = function() {
- return this.tag == 'VCARD';
+ return this.tag == 'vcard';
};
Card.prototype.$isList = function() {
- return this.tag == 'VLIST';
- };
-
- Card.prototype.$delete = function(attribute, index) {
- if (index > -1 && this[attribute].length > index) {
- this[attribute].splice(index, 1);
- }
+ return this.tag == 'vlist';
};
Card.prototype.$addOrgUnit = function(orgUnit) {
@@ -226,24 +266,21 @@
return this.addresses.length - 1;
};
- /* never call for now */
- Card.$unwrapCollection = function(futureCardData) {
- var collection = {};
+ // Unwrap a promise
+ Card.prototype.$unwrap = function(futureCardData) {
+ var self = this;
- collection.$futureCardData = futureCardData;
-
- futureCardData.then(function(cards) {
+ this.$futureCardData = futureCardData;
+ this.$futureCardData.then(function(data) {
+ // The success callback. Calling _.extend from $timeout will wrap it into a try/catch call and return
+ // a promise resolved immediately.
Card.$timeout(function() {
- angular.forEach(cards, function(data, index) {
- collection[data.id] = new Card(data);
- });
+ angular.extend(self, data);
});
});
-
- return collection;
};
- // $omit returns a sanitized object used to send to the server
+ // Return a sanitized object used to send to the server
Card.prototype.$omit = function() {
var card = {};
angular.forEach(this, function(value, key) {
@@ -253,11 +290,4 @@
});
return card;
};
-
- Card.prototype.$save = function() {
- return Card.$$resource.set([this.pid, this.id].join('/'), this.$omit()).then(function (data) {
- return data;
- });
- };
-
})();
diff --git a/UI/WebServerResources/js/ContactsUI.js b/UI/WebServerResources/js/ContactsUI.js
index 698bb7189..8cfe578e2 100644
--- a/UI/WebServerResources/js/ContactsUI.js
+++ b/UI/WebServerResources/js/ContactsUI.js
@@ -6,7 +6,7 @@
angular.module('SOGo.Common', []);
- angular.module('SOGo.Contacts', ['ngSanitize', 'ui.router', 'mm.foundation', 'mm.foundation.offcanvas', 'SOGo.Common'])
+ angular.module('SOGo.Contacts', ['ngSanitize', 'ui.router', 'mm.foundation', 'mm.foundation.offcanvas', 'SOGo.Common', 'SOGo.UIDesktop'])
.constant('sgSettings', {
'baseURL': ApplicationBaseURL
@@ -31,6 +31,15 @@
controller: 'cardCtrl'
}
}
+ })
+ .state('addressbook.new', {
+ url: "/:contact_type/new",
+ views: {
+ 'card': {
+ templateUrl: "card.html",
+ controller: 'cardCtrl'
+ }
+ }
});
// if none of the above states are matched, use this as the fallback
@@ -56,31 +65,36 @@
}
}])
- .controller('AddressBookCtrl', ['$state', '$scope', '$rootScope', '$stateParams', '$timeout', '$modal', 'sgFocus', 'sgCard', 'sgAddressBook', function($state, $scope, $rootScope, $stateParams, $timeout, $modal, focus, Card, AddressBook) {
+ .controller('AddressBookCtrl', ['$state', '$scope', '$rootScope', '$stateParams', '$timeout', '$modal', 'sgFocus', 'sgCard', 'sgAddressBook', 'sgDialog', function($state, $scope, $rootScope, $stateParams, $timeout, $modal, focus, Card, AddressBook, Dialog) {
+ // $scope objects
$scope.search = { 'status': null, 'filter': null, 'last_filter': null };
+
if ($stateParams.addressbook_id &&
($rootScope.addressbook == undefined || $stateParams.addressbook_id != $rootScope.addressbook.id)) {
- // Selected addressbook has changed
+ // Selected addressbook has changed; fetch list of contacts
$rootScope.addressbook = AddressBook.$find($stateParams.addressbook_id);
- // Extend resulting model instance with parameters from addressbooks listing
+ // Adjust search status depending on addressbook type
var o = _.find($rootScope.addressbooks, function(o) {
return o.id == $stateParams.addressbook_id;
});
- $scope.search.status = (o.isRemote)? 'remote-addressbook' : '';
+ $scope.search.status = (o && o.isRemote)? 'remote-addressbook' : '';
}
// Initialize with data from template
$scope.init = function() {
$rootScope.addressbooks = AddressBook.$all(contactFolders);
};
+ // $scope functions
$scope.select = function(rowIndex) {
$scope.editMode = false;
};
$scope.edit = function(i) {
- if (angular.isUndefined(i)) {
- i = _.indexOf(_.pluck($rootScope.addressbooks, 'id'), $rootScope.addressbook.id);
+ if (!$rootScope.addressbook.isRemote) {
+ if (angular.isUndefined(i)) {
+ i = _.indexOf(_.pluck($rootScope.addressbooks, 'id'), $rootScope.addressbook.id);
+ }
+ $scope.editMode = $rootScope.addressbook.id;
+ focus('addressBookName_' + i);
}
- $scope.editMode = $rootScope.addressbook.id;
- focus('addressBookName_' + i);
};
$scope.save = function(i) {
$rootScope.addressbook.name = $rootScope.addressbooks[i].name;
@@ -92,7 +106,22 @@
console.debug("failed");
});
};
- $scope.sharing = function() {
+ $scope.confirmDelete = function() {
+ Dialog.confirm(l('Warning'), l('Are you sure you want to delete the addressbook "%{0}"?',
+ $rootScope.addressbook.name), function() {
+ $rootScope.addressbook.$delete()
+ .then(function() {
+ $rootScope.addressbooks = _.reject($rootScope.addressbooks, function(o) {
+ return o.id == $rootScope.addressbook.id;
+ });
+ $rootScope.addressbook = null;
+ }, function(data, status) {
+ Dialog.alert(l('Warning'), l('An error occured while deleting the addressbook "%{0}".',
+ $rootScope.addressbook.name));
+ });
+ });
+ };
+ $scope.share = function() {
var modal = $modal.open({
templateUrl: 'addressbookSharing.html',
//controller: 'addressbookSharingCtrl'
@@ -130,26 +159,27 @@
};
}])
- // angular.module('SOGo').controller('addressbookSharingCtrl', ['$scope', '$modalInstance', function($scope, modal) {
- // $scope.closeModal = function() {
- // console.debug('please close it');
- // modal.close();
- // };
- // }]);
-
- .controller('cardCtrl', ['$scope', '$rootScope', 'sgAddressBook', 'sgCard', 'sgFocus', '$stateParams', function($scope, $rootScope, AddressBook, Card, focus, $stateParams) {
+ .controller('cardCtrl', ['$scope', '$rootScope', 'sgAddressBook', 'sgCard', 'sgDialog', 'sgFocus', '$state', '$stateParams', function($scope, $rootScope, AddressBook, Card, Dialog, focus, $state, $stateParams) {
if ($stateParams.card_id) {
+ // Show existing card
if ($rootScope.addressbook == null) {
// Card is directly access with URL fragment
$rootScope.addressbook = AddressBook.$find($stateParams.addressbook_id);
}
- $rootScope.addressbook.$getCard($stateParams.card_id);
+ $rootScope.addressbook.$getCard($stateParams.card_id)
$scope.editMode = false;
}
+ else if ($stateParams.contact_type) {
+ // Create new card or list
+ var tag = 'v' + $stateParams.contact_type;
+ $scope.addressbook.card = new Card({ 'pid': $stateParams.addressbook_id, 'tag': tag });
+ $scope.editMode = true;
+ }
$scope.allEmailTypes = Card.$email_types;
$scope.allTelTypes = Card.$tel_types;
$scope.allUrlTypes = Card.$url_types;
$scope.allAddressTypes = Card.$address_types;
+
$scope.edit = function() {
$rootScope.master_card = angular.copy($rootScope.addressbook.card);
$scope.editMode = true;
@@ -180,12 +210,20 @@
focus('address_' + i);
};
$scope.save = function(cardForm) {
- console.debug("save");
if (cardForm.$valid) {
$rootScope.addressbook.card.$save()
.then(function(data) {
console.debug("saved!");
$scope.editMode = false;
+ var i = _.indexOf(_.pluck($rootScope.addressbook.cards, 'id'), $rootScope.addressbook.card.id);
+ if (i < 0) {
+ // Reload contacts list and show addressbook in which the card has been created
+ $rootScope.addressbook = AddressBook.$find(data.pid);
+ }
+ else {
+ // Update contacts list with new version of the Card object
+ $rootScope.addressbook.cards[i] = angular.copy($rootScope.addressbook.card);
+ }
}, function(data, status) {
console.debug("failed");
});
@@ -198,6 +236,22 @@
$scope.reset = function() {
$rootScope.addressbook.card = angular.copy($rootScope.master_card);
};
+ $scope.confirmDelete = function(card) {
+ Dialog.confirm(l('Warning'),
+ l('Are you sure you want to delete the card of "%{0}"?', card.$fullname()),
+ function() {
+ card.$delete()
+ .then(function() {
+ $rootScope.addressbook.cards = _.reject($rootScope.addressbook.cards, function(o) {
+ return o.id == card.id;
+ });
+ $rootScope.addressbook.card = null;
+ }, function(data, status) {
+ Dialog.alert(l('Warning'), l('An error occured while deleting the card "%{0}".',
+ card.$fullname()));
+ });
+ });
+ };
}]);
})();
diff --git a/UI/WebServerResources/scss/ContactsUI.scss b/UI/WebServerResources/scss/ContactsUI.scss
index 646f5ac9c..278d171a2 100644
--- a/UI/WebServerResources/scss/ContactsUI.scss
+++ b/UI/WebServerResources/scss/ContactsUI.scss
@@ -9,7 +9,7 @@ $module-color: #6F5A73;
$module-secondary-color: #8EC588; // light green
$module-secondary-color: #3D792A; // green
$module-secondary-color: #B996BF;
-$module-secondary-color: #B59BB9;
+$module-secondary-color: #B59BB9; // SOGo green
$module-light-color: #F7ECFF;
$topbar-bg-color: $module-color;
$topbar-link-bg-active-hover: scale-color($module-secondary-color, $lightness: -14%);
@@ -66,6 +66,7 @@ $topbar-link-bg-hover: scale-color($module-color, $lightness: -14%);
//vertical-align: middle;
}
.icon {
+ //vertical-align: middle; // causes glitch when selecting row
width: 2em;
}
form {
@@ -77,7 +78,7 @@ $topbar-link-bg-hover: scale-color($module-color, $lightness: -14%);
display: block;
width: 100%;
padding: 0;
- padding-left: $off-canvas-link-padding;
+ //padding-left: $off-canvas-link-padding;
}
input {
border: 0;
@@ -117,7 +118,7 @@ $column-gutter: 0;
height: 40px;
border-radius: 20px;
color: $input-disabled-bg;
- line-height: 40px;
+ //line-height: 40px;
text-align: center;
border: 1px solid $input-disabled-bg;
margin-right: 1em;
@@ -130,6 +131,15 @@ $column-gutter: 0;
#pageContent {
@include grid-row($behavior: nest);
+ #newListDrop {
+ a {
+ text-transform: uppercase;
+ &.tiny {
+ @include button-size($padding:$button-tny);
+ }
+ }
+ }
+
#addressbooksList {
position: absolute;
overflow: auto;
@@ -393,14 +403,14 @@ $column-gutter: 0;
}
input {
display: table-cell;
- /* transition: background-color 300ms ease; */
+ //transition: all 300ms ease;
//background-color: $secondary-color;
background-color: transparent;
border: 0;
box-shadow: none;
margin-bottom: 0;
/* &:focus { */
- /* background-color: #fff; */
+ /* border: 1px solid blue; */
/* } */
}
}
@@ -439,21 +449,6 @@ $column-gutter: 0;
}
.f-dropdown {
- dd {
- a {
- background-color: $module-secondary-color;
- color: #fff;
- &:hover {
- background-color: $module-color;
- }
- }
- &.active {
- a {
- background-color: $module-color;
- color: #fff;
- }
- }
- }
&.icons-dropdown {
width: auto;
//background-color: $primary-color;