Webmail: add possibility to rename a mailbox

This commit is contained in:
Francis Lachapelle
2015-01-08 11:52:10 -05:00
parent 59e84f8b93
commit 8a4f9a9c8a
4 changed files with 272 additions and 61 deletions
+3 -3
View File
@@ -216,11 +216,11 @@
<script type="text/ng-template" id="mailboxes.html">
<!-- dropdown menu for addressbook options button -->
<!-- dropdown menu for mailbox options button -->
<div id="folderProperties" class="f-dropdown icons-dropdown" data-dropdown-content="dropdown-content">
<ul class="button-group">
<li data-ng-show="currentFolder.isEditable">
<span class="button" data-ng-click="edit()"><i class="icon-pencil"><!-- rename --></i></span>
<span class="button" data-ng-click="editFolder(currentFolder)"><i class="icon-pencil"><!-- rename --></i></span>
</li>
<li data-ng-show="currentFolder.isEditable">
<span class="button" data-ng-click="newFolder(currentFolder)"><i class="icon-plus"><!-- new mailbox --></i></span>
@@ -251,7 +251,7 @@
<sg-folder-tree data-ng-repeat="folder in account.$mailboxes track by folder.id"
data-sg-root="account"
data-sg-folder="folder"
data-sg-set-folder="setCurrentFolder"><!-- tree --></sg-folder-tree>
data-sg-select-folder="setCurrentFolder"><!-- tree --></sg-folder-tree>
</ul>
</div>
</div>
+134 -32
View File
@@ -115,11 +115,16 @@
.factory('sgDialog', Dialog.$factory)
/**
* @desc A directive evaluated when the escape key is pressed.
* sgEscape - A directive evaluated when the escape key is pressed
* @memberof SOGo.UIDesktop
* @example:
<input type="text"
sg-escape="revertEditing($index)" />
*/
.directive('sgEscape', function() {
var ESCAPE_KEY = 27;
return function (scope, elem, attrs) {
return function(scope, elem, attrs) {
elem.bind('keydown', function (event) {
if (event.keyCode === ESCAPE_KEY) {
scope.$apply(attrs.sgEscape);
@@ -128,17 +133,54 @@
};
})
/**
* sgFocusOn - A directive that sets the focus on its element when the specified string is broadcasted
* @memberof SOGo.UIDesktop
* @see {@link SOGo.UIDesktop.sgFocus}
* @example:
<input type="text"
sg-focus-on="username" />
*/
.directive('sgFocusOn', function() {
return function(scope, elem, attr) {
scope.$on('sgFocusOn', function(e, name) {
if (name === attr.sgFocusOn) {
elem[0].focus();
elem[0].select();
}
});
};
})
/**
* sgFocus - A service to set the focus on the element associated to a specific string
* @memberof SOGo.UIDesktop
* @param {string} name - the string identifier of the element
* @see {@link SOGo.UIDesktop.sgFocusOn}
*/
.factory('sgFocus', ['$rootScope', '$timeout', function($rootScope, $timeout) {
return function(name) {
$timeout(function() {
$rootScope.$broadcast('sgFocusOn', name);
});
}
}])
/*
* sgFolderTree - Provides hierarchical folders tree
* @memberof SOGo.UIDesktop
* @restrict element
* @param {object} sgRoot
* @param {object} sgFolder
* @param {function} sgSetFolder
* @see https://github.com/marklagendijk/angular-recursion
* @example:
<sg-folder-tree data-ng-repeat="folder in folders track by folder.id"
data-sg-root="account"
data-sg-folder="folder"
data-sg-set-folder="setCurrentFolder"><!-- tree --></sg-folder-tree>
<sg-folder-tree ng-repeat="folder in folders track by folder.id"
sg-root="account"
sg-folder="folder"
sg-select-folder="setCurrentFolder"><!-- tree --></sg-folder-tree>
*/
.directive('sgFolderTree', function(RecursionHelper) {
return {
@@ -146,15 +188,19 @@
scope: {
root: '=sgRoot',
folder: '=sgFolder',
setFolder: '=sgSetFolder'
selectFolder: '=sgSelectFolder'
},
template:
'<li>' +
' <span class="folder-container">' +
' <span class="folder-content">' +
' <i class="icon icon-ion-folder"></i>' +
' <form>' +
' <a data-ng-cloak="ng-cloak">{{folder.name}}</a>' +
' <form data-ng-submit="save()">' +
' <a>{{folder.name}}</a>' +
' <input type="text" class="folder-name ng-hide"' +
' data-ng-model="folder.name"' +
' data-ng-blur="save()"' +
' data-sg-escape="revert()"/>' +
' </form>' +
' <span class="icon ng-hide" data-ng-cloak="ng-cloak">' +
' <a class="icon" href="#"' +
@@ -164,39 +210,69 @@
' </span>' +
' </span>' +
'</li>' +
'<sg-folder-tree ng-repeat="child in folder.children track by child.path" data-sg-root="root" data-sg-folder="child" data-sg-set-folder="setFolder"></sg-folder-tree>',
'<sg-folder-tree ng-repeat="child in folder.children track by child.path"' +
' data-sg-root="root"' +
' data-sg-folder="child"' +
' data-sg-select-folder="selectFolder"></sg-folder-tree>',
compile: function(element) {
return RecursionHelper.compile(element, function(scope, iElement, iAttrs, controller, transcludeFn) {
var level, link, input, edit;
// Set CSS class for folder hierarchical level
var level = scope.folder.path.split('/').length - 1;
level = scope.folder.path.split('/').length - 1;
angular.element(iElement.find('i')[0]).addClass('childLevel' + level);
var link = iElement.find('a');
// Select dynamic elements
link = angular.element(iElement.find('a')[0]);
input = iElement.find('input')[0];
var edit = function() {
link.addClass('ng-hide');
angular.element(input).removeClass('ng-hide');
input.focus();
input.select();
};
// jQLite listeners
// click - call the directive's external function sgSelectFolder
link.on('click', function() {
var list = iElement.parent();
while (list[0].tagName != 'UL') {
list = list.parent();
var list, items;
if (!scope.mode.selected) {
list = iElement.parent();
while (list[0].tagName != 'UL') {
list = list.parent();
}
items = list.find('li');
// Highlight element as "loading"
items.removeClass('_selected');
items.removeClass('_loading');
angular.element(iElement.find('li')[0]).addClass('_loading');
// Call external function
scope.selectFolder(scope.root, scope.folder);
}
var items = list.find('li');
// Highlight element as "loading"
items.removeClass('_selected');
items.removeClass('_loading');
angular.element(iElement.find('li')[0]).addClass('_loading');
// Call external function
scope.setFolder(scope.root, scope.folder);
});
// TODO: rename folder on dbl-click
// link.on('dblclick', function() {
// });
// dblclick - enter edit mode
link.on('dblclick', function() {
edit();
});
// Broadcast listeners
// sgSelectFolder - broadcasted when the folder has been successfully loaded
scope.$on('sgSelectFolder', function(event, folderId) {
if (folderId == scope.folder.id) {
var list = iElement.parent();
while (list[0].tagName != 'UL') {
list = list.parent();
}
var items = list.find('li');
var list = iElement.parent(),
items;
scope.mode.selected = true;
while (list[0].tagName != 'UL') {
list = list.parent();
}
items = list.find('li');
// Hightlight element as "selected"
angular.element(iElement.find('li')[0]).removeClass('_loading');
@@ -210,7 +286,33 @@
});
angular.element(iElement.find('span')[2]).removeClass('ng-hide');
}
else {
scope.mode.selected = false;
}
});
// sgEditFolder - broadcasted when the user wants to rename the folder
scope.$on('sgEditFolder', function(event, folderId) {
if (scope.mode.selected && folderId == scope.folder.id) {
edit();
}
});
// Local scope variables and functions
scope.mode = { selected: false };
scope.save = function() {
if (link.hasClass('ng-hide')) {
angular.element(input).addClass('ng-hide');
link.removeClass('ng-hide');
scope.$emit('sgSaveFolder', scope.folder.id);
}
};
scope.revert = function() {
scope.$emit('sgRevertFolder', scope.folder.id);
};
});
}
};
@@ -21,6 +21,8 @@
else {
this.id = this.$id();
this.isEditable = this.$isEditable();
// Make a copy of the data for an eventual reset
this.$shadowData = this.$omit();
}
}
else {
@@ -127,7 +129,7 @@
return path.join('/');
};
/**
* @function $id
* @memberof Mailbox.prototype
@@ -207,6 +209,77 @@
return _.contains(['folder', 'inbox', 'draft', 'sent', 'trash'], this.type);
};
/**
* @function $rename
* @memberof AddressBook.prototype
* @desc Rename the addressbook and keep the list sorted
* @param {string} name - the new name
* @returns a promise of the HTTP operation
*/
Mailbox.prototype.$rename = function() {
var _this = this,
findParent,
deferred = Mailbox.$q.defer(),
parent,
children,
i;
// Local recursive function
findParent = function(parent, children) {
var parentMailbox = null,
mailbox = _.find(children, function(o) {
return o.path == _this.path;
});
if (mailbox) {
parentMailbox = parent;
}
else {
angular.forEach(children, function(o) {
if (!parentMailbox && o.children && o.children.length > 0) {
parentMailbox = findParent(o, o.children);
}
});
}
return parentMailbox;
};
// Find mailbox parent
parent = findParent(null, this.$account.$mailboxes);
if (parent == null)
children = this.$account.$mailboxes;
else
children = parent.children;
// Find index of mailbox among siblings
i = _.indexOf(_.pluck(children, 'id'), this.id);
this.$save().then(function(data) {
var sibling;
angular.extend(_this, data); // update the path attribute
_this.id = _this.$id();
// Move mailbox among its siblings according to its new name
children.splice(i, 1);
sibling = _.find(children, function(o) {
Mailbox.$log.debug(o.name + ' ? ' + _this.name);
return (o.type == 'folder' && o.name.localeCompare(_this.name) > 0);
});
if (sibling) {
i = _.indexOf(_.pluck(children, 'id'), sibling.id);
}
else {
i = children.length;
}
children.splice(i, 0, _this);
deferred.resolve();
}, function(data) {
deferred.reject(data);
});
return deferred.promise;
};
/**
* @function $delete
* @memberof Mailbox.prototype
@@ -215,18 +288,18 @@
*/
Mailbox.prototype.$delete = function() {
var _this = this,
d = Mailbox.$q.defer(),
deferred = Mailbox.$q.defer(),
promise;
promise = Mailbox.$$resource.remove(this.id);
promise.then(function() {
_this.$account.$getMailboxes({reload: true});
d.resolve(true);
deferred.resolve(true);
}, function(data, status) {
d.reject(data);
deferred.reject(data);
});
return d.promise;
return deferred.promise;
};
/**
@@ -239,6 +312,43 @@
return Mailbox.$$resource.post(this.id, 'batchDelete', {uids: uids});
};
/**
* @function $reset
* @memberof Mailbox.prototype
* @desc Reset the original state the mailbox's data.
*/
Mailbox.prototype.$reset = function() {
var _this = this;
angular.forEach(this, function(value, key) {
if (key != 'constructor' && key != 'children' && key[0] != '$') {
delete _this[key];
}
});
angular.extend(this, this.$shadowData);
this.$shadowData = this.$omit();
};
/**
* @function $save
* @memberof Mailbox.prototype
* @desc Save the mailbox to the server. This currently can only affect the name of the mailbox.
* @returns a promise of the HTTP operation
*/
Mailbox.prototype.$save = function() {
var _this = this;
return Mailbox.$$resource.save(this.id, this.$omit()).then(function(data) {
// Make a copy of the data for an eventual reset
_this.$shadowData = _this.$omit();
Mailbox.$log.debug(JSON.stringify(data, undefined, 2));
return data;
}, function(data) {
Mailbox.$log.error(JSON.stringify(data, undefined, 2));
// Restore previous version
_this.$reset();
});
};
/**
* @function $newMailbox
* @memberof Account.prototype
@@ -360,5 +470,5 @@
});
});
};
})();
+19 -20
View File
@@ -163,29 +163,10 @@
.run(function($rootScope) {
$rootScope.$on('$routeChangeError', function(event, current, previous, rejection) {
console.log(event, current, previous, rejection)
console.error(event, current, previous, rejection)
})
})
.directive('sgFocusOn', function() {
return function(scope, elem, attr) {
scope.$on('sgFocusOn', function(e, name) {
if (name === attr.sgFocusOn) {
elem[0].focus();
elem[0].select();
}
});
};
})
.factory('sgFocus', ['$rootScope', '$timeout', function($rootScope, $timeout) {
return function(name) {
$timeout(function() {
$rootScope.$broadcast('sgFocusOn', name);
});
}
}])
.controller('MailboxesCtrl', ['$scope', '$rootScope', '$stateParams', '$state', '$timeout', '$modal', 'sgFocus', 'encodeUriFilter', 'sgDialog', 'sgAccount', 'sgMailbox', 'stateAccounts', function($scope, $rootScope, $stateParams, $state, $timeout, $modal, focus, encodeUriFilter, Dialog, Account, Mailbox, stateAccounts) {
$scope.accounts = stateAccounts;
@@ -198,6 +179,9 @@
}
});
};
$scope.editFolder = function(folder) {
$rootScope.$broadcast('sgEditFolder', folder.id);
};
$scope.setCurrentFolder = function(account, folder) {
$rootScope.currentFolder = folder;
$state.go('mail.account.mailbox', { accountId: account.id, mailboxId: encodeUriFilter(folder.path) });
@@ -221,7 +205,22 @@
});
};
// Register listeners
$scope.$on('sgRevertFolder', function(event, folderId) {
if (folderId == $scope.currentFolder.id) {
$scope.currentFolder.$reset();
event.stopPropagation();
}
});
$scope.$on('sgSaveFolder', function(event, folderId) {
if (folderId == $scope.currentFolder.id) {
$scope.currentFolder.$rename();
event.stopPropagation();
}
});
if ($state.current.name == 'mail' && $scope.accounts.length > 0 && $scope.accounts[0].$mailboxes.length > 0) {
// Redirect to first mailbox of first account if no mailbox is selected
var account = $scope.accounts[0];
var mailbox = account.$mailboxes[0];
$state.go('mail.account.mailbox', { accountId: account.id, mailboxId: encodeUriFilter(mailbox.path) });