mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-05-05 19:45:26 +00:00
Webmail: add possibility to rename a mailbox
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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 @@
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
})();
|
||||
|
||||
@@ -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) });
|
||||
|
||||
Reference in New Issue
Block a user