(js) Add batch operations in advanced search

pull/217/head
Francis Lachapelle 2016-06-23 16:03:14 -04:00
parent cf3a9b89b6
commit 13e826b6f2
5 changed files with 228 additions and 177 deletions

1
NEWS
View File

@ -7,6 +7,7 @@ New features
Enhancements Enhancements
- [eas] use the preferred email identity in EAS if valid (#3698) - [eas] use the preferred email identity in EAS if valid (#3698)
- [eas] handle inline attachments during EAS content generation - [eas] handle inline attachments during EAS content generation
- [web] all batch operations can now be performed on selected messages in advanced search mode
Bug fixes Bug fixes
- [web] fixed crash when an attachment filename has no extension - [web] fixed crash when an attachment filename has no extension

View File

@ -65,11 +65,11 @@
<!-- sort mode (default) --> <!-- sort mode (default) -->
<div class="md-toolbar-tools" ng-hide="mailbox.mode.search"> <div class="md-toolbar-tools" ng-hide="mailbox.mode.search">
<md-button class="sg-icon-button" label:aria-label="Search" <md-button class="sg-icon-button" label:aria-label="Search"
ng-click="mailbox.mode.search = true"> ng-click="mailbox.searchMode()">
<md-icon>search</md-icon> <md-icon>search</md-icon>
</md-button> </md-button>
<a href="javascript:void(0)" class="sg-folder-name" <a href="javascript:void(0)" class="sg-folder-name"
ng-click="mailbox.mode.search = true">{{mailbox.selectedFolder.name}}</a> ng-click="mailbox.searchMode()">{{mailbox.selectedFolder.name}}</a>
<md-menu> <md-menu>
<md-button class="sg-icon-button" label:aria-label="Sort" <md-button class="sg-icon-button" label:aria-label="Sort"
ng-click="$mdOpenMenu()"> ng-click="$mdOpenMenu()">
@ -128,7 +128,7 @@
<md-icon>arrow_back</md-icon> <md-icon>arrow_back</md-icon>
</md-button> </md-button>
<md-input-container class="md-flex" md-no-float="md-no-float"> <md-input-container class="md-flex" md-no-float="md-no-float">
<input name="folderSearch" type="search" var:minlength="minimumSearchLength" label:placeholder="Search"/> <input name="folderSearch" type="search" var:minlength="minimumSearchLength" label:placeholder="Search" sg-focus-on="search" />
<div ng-messages="searchForm.folderSearch.$error" ng-show="searchForm.folderSearch.$dirty"> <div ng-messages="searchForm.folderSearch.$error" ng-show="searchForm.folderSearch.$dirty">
<div ng-message="minlength"><var:string value="minimumSearchLengthLabel"/></div> <div ng-message="minlength"><var:string value="minimumSearchLengthLabel"/></div>
</div> </div>

View File

@ -221,6 +221,16 @@
return Mailbox.$absolutePath(this.$account.id, this.path); return Mailbox.$absolutePath(this.$account.id, this.path);
}; };
/**
* @function $selectedMessages
* @memberof Mailbox.prototype
* @desc Return the messages selected by the user.
* @returns Message instances
*/
Mailbox.prototype.$selectedMessages = function() {
return _.filter(this.$messages, function(message) { return message.selected; });
};
/** /**
* @function $selectedCount * @function $selectedCount
* @memberof Mailbox.prototype * @memberof Mailbox.prototype
@ -228,13 +238,7 @@
* @returns the number of selected messages * @returns the number of selected messages
*/ */
Mailbox.prototype.$selectedCount = function() { Mailbox.prototype.$selectedCount = function() {
var count; return this.$selectedMessages().length;
count = 0;
if (this.$messages) {
count = (_.filter(this.$messages, function(message) { return message.selected; })).length;
}
return count;
}; };
/** /**
@ -542,12 +546,14 @@
* @desc Add or remove a flag on a message set * @desc Add or remove a flag on a message set
* @returns a promise of the HTTP operation * @returns a promise of the HTTP operation
*/ */
Mailbox.prototype.$flagMessages = function(uids, flags, operation) { Mailbox.prototype.$flagMessages = function(messages, flags, operation) {
var data = {msgUIDs: uids, var data = {msgUIDs: _.map(messages, 'uid'),
flags: flags, flags: flags,
operation: operation}; operation: operation};
return Mailbox.$$resource.post(this.id, 'addOrRemoveLabel', data); return Mailbox.$$resource.post(this.id, 'addOrRemoveLabel', data).then(function() {
return messages;
});
}; };
/** /**
@ -656,9 +662,9 @@
* @return a promise of the HTTP operation * @return a promise of the HTTP operation
*/ */
Mailbox.prototype.$markOrUnMarkMessagesAsJunk = function(messages) { Mailbox.prototype.$markOrUnMarkMessagesAsJunk = function(messages) {
var _this = this, uids; var _this = this,
var method = (this.type == 'junk' ? 'markMessagesAsNotJunk' : 'markMessagesAsJunk'); uids = _.map(messages, 'uid'),
uids = _.map(messages, 'uid'); method = (this.type == 'junk' ? 'markMessagesAsNotJunk' : 'markMessagesAsJunk');
return Mailbox.$$resource.post(this.id, method, {uids: uids}); return Mailbox.$$resource.post(this.id, method, {uids: uids});
}; };
@ -669,8 +675,9 @@
* @desc Copy multiple messages from the current mailbox to a target one * @desc Copy multiple messages from the current mailbox to a target one
* @return a promise of the HTTP operation * @return a promise of the HTTP operation
*/ */
Mailbox.prototype.$copyMessages = function(uids, folder) { Mailbox.prototype.$copyMessages = function(messages, folder) {
var _this = this; var _this = this,
uids = _.map(messages, 'uid');
return Mailbox.$$resource.post(this.id, 'copyMessages', {uids: uids, folder: folder}) return Mailbox.$$resource.post(this.id, 'copyMessages', {uids: uids, folder: folder})
.then(function(data) { .then(function(data) {

View File

@ -6,8 +6,8 @@
/** /**
* @ngInject * @ngInject
*/ */
MailboxController.$inject = ['$window', '$timeout', '$state', '$mdDialog', 'stateAccounts', 'stateAccount', 'stateMailbox', 'encodeUriFilter', 'Dialog', 'Account', 'Mailbox']; MailboxController.$inject = ['$window', '$timeout', '$q', '$state', '$mdDialog', 'stateAccounts', 'stateAccount', 'stateMailbox', 'encodeUriFilter', 'sgFocus', 'Dialog', 'Account', 'Mailbox'];
function MailboxController($window, $timeout, $state, $mdDialog, stateAccounts, stateAccount, stateMailbox, encodeUriFilter, Dialog, Account, Mailbox) { function MailboxController($window, $timeout, $q, $state, $mdDialog, stateAccounts, stateAccount, stateMailbox, encodeUriFilter, focus, Dialog, Account, Mailbox) {
var vm = this, messageDialog = null; var vm = this, messageDialog = null;
// Expose controller // Expose controller
@ -21,7 +21,12 @@
vm.selectedFolder = stateMailbox; vm.selectedFolder = stateMailbox;
vm.selectMessage = selectMessage; vm.selectMessage = selectMessage;
vm.toggleMessageSelection = toggleMessageSelection; vm.toggleMessageSelection = toggleMessageSelection;
vm.unselectMessages = unselectMessages; vm.sort = sort;
vm.sortedBy = sortedBy;
vm.searchMode = searchMode;
vm.cancelSearch = cancelSearch;
vm.newMessage = newMessage;
vm.mode = { search: false, multiple: 0 };
vm.confirmDeleteSelectedMessages = confirmDeleteSelectedMessages; vm.confirmDeleteSelectedMessages = confirmDeleteSelectedMessages;
vm.markOrUnMarkMessagesAsJunk = markOrUnMarkMessagesAsJunk; vm.markOrUnMarkMessagesAsJunk = markOrUnMarkMessagesAsJunk;
vm.copySelectedMessages = copySelectedMessages; vm.copySelectedMessages = copySelectedMessages;
@ -29,160 +34,7 @@
vm.markSelectedMessagesAsFlagged = markSelectedMessagesAsFlagged; vm.markSelectedMessagesAsFlagged = markSelectedMessagesAsFlagged;
vm.markSelectedMessagesAsUnread = markSelectedMessagesAsUnread; vm.markSelectedMessagesAsUnread = markSelectedMessagesAsUnread;
vm.selectAll = selectAll; vm.selectAll = selectAll;
vm.sort = sort; vm.unselectMessages = unselectMessages;
vm.sortedBy = sortedBy;
vm.cancelSearch = cancelSearch;
vm.newMessage = newMessage;
vm.mode = { search: false, multiple: 0 };
function selectMessage(message) {
if (Mailbox.$virtualMode)
$state.go('mail.account.virtualMailbox.message', {accountId: stateAccount.id, mailboxId: encodeUriFilter(message.$mailbox.path), messageId: message.uid});
else
$state.go('mail.account.mailbox.message', {messageId: message.uid});
}
function toggleMessageSelection($event, message) {
message.selected = !message.selected;
vm.mode.multiple += message.selected? 1 : -1;
$event.preventDefault();
$event.stopPropagation();
}
function unselectMessages() {
_.forEach(vm.selectedFolder.$messages, function(message) {
message.selected = false;
});
vm.mode.multiple = 0;
}
function confirmDeleteSelectedMessages() {
Dialog.confirm(l('Warning'),
l('Are you sure you want to delete the selected messages?'),
{ ok: l('Delete') })
.then(function() {
var deleteSelectedMessage = false;
var selectedMessages = _.filter(vm.selectedFolder.$messages, function(message) {
if (message.selected &&
message.uid == vm.selectedFolder.selectedMessage)
deleteSelectedMessage = true;
return message.selected;
});
vm.selectedFolder.$deleteMessages(selectedMessages).then(function(index) {
unselectMessage(deleteSelectedMessage, index);
});
});
}
function markOrUnMarkMessagesAsJunk() {
var moveSelectedMessage = false;
var selectedMessages = _.filter(vm.selectedFolder.$messages, function(message) {
if (message.selected &&
message.uid == vm.selectedFolder.selectedMessage)
moveSelectedMessage = true;
return message.selected;
});
vm.selectedFolder.$markOrUnMarkMessagesAsJunk(selectedMessages).then(function() {
var folder = '/' + vm.account.id + '/folderINBOX';
if (vm.selectedFolder.type != 'junk') {
folder = '/' + vm.account.$getMailboxByType('junk').id;
}
vm.selectedFolder.$moveMessages(selectedMessages, folder).then(function(index) {
unselectMessage(moveSelectedMessage, index);
});
});
}
function unselectMessage(message, index) {
// Unselect current message and cleverly load the next message
var nextMessage, previousMessage, nextIndex = index;
vm.mode.multiple = vm.selectedFolder.$selectedCount();
if (message) {
if (Mailbox.$virtualMode) {
$state.go('mail.account.virtualMailbox');
}
else {
// Select either the next or previous message
if (index > 0) {
nextIndex -= 1;
nextMessage = vm.selectedFolder.$messages[nextIndex];
}
if (index < vm.selectedFolder.$messages.length)
previousMessage = vm.selectedFolder.$messages[index];
if (nextMessage) {
if (nextMessage.isread && previousMessage && !previousMessage.isread) {
nextIndex = index;
nextMessage = previousMessage;
}
}
else if (previousMessage) {
nextIndex = index;
nextMessage = previousMessage;
}
if (nextMessage) {
$state.go('mail.account.mailbox.message', { messageId: nextMessage.uid });
vm.selectedFolder.$topIndex = nextIndex;
}
else {
$state.go('mail.account.mailbox');
}
}
}
}
function copySelectedMessages(folder) {
var selectedMessages = _.filter(vm.selectedFolder.$messages, function(message) { return message.selected; });
var selectedUIDs = _.map(selectedMessages, 'uid');
vm.selectedFolder.$copyMessages(selectedUIDs, '/' + folder);
}
function moveSelectedMessages(folder) {
var moveSelectedMessage = false;
var selectedMessages = _.filter(vm.selectedFolder.$messages, function(message) {
if (message.selected &&
message.uid == vm.selectedFolder.selectedMessage)
moveSelectedMessage = true;
return message.selected;
});
vm.selectedFolder.$moveMessages(selectedMessages, '/' + folder).then(function(index) {
unselectMessage(moveSelectedMessage, index);
});
}
function selectAll() {
var i = 0, length = vm.selectedFolder.$messages.length;
for (; i < length; i++)
vm.selectedFolder.$messages[i].selected = true;
vm.mode.multiple = length;
}
function markSelectedMessagesAsFlagged() {
var selectedMessages = _.filter(vm.selectedFolder.$messages, function(message) { return message.selected; });
var selectedUIDs = _.map(selectedMessages, 'uid');
vm.selectedFolder.$flagMessages(selectedUIDs, '\\Flagged', 'add').then(function(d) {
// Success
_.forEach(selectedMessages, function(message) {
message.isflagged = true;
});
});
}
function markSelectedMessagesAsUnread() {
var selectedMessages = _.filter(vm.selectedFolder.$messages, function(message) { return message.selected; });
var selectedUIDs = _.map(selectedMessages, 'uid');
vm.selectedFolder.$flagMessages(selectedUIDs, 'seen', 'remove').then(function(d) {
// Success
_.forEach(selectedMessages, function(message) {
message.isread = false;
vm.selectedFolder.unseenCount++;
});
});
}
function sort(field) { function sort(field) {
vm.selectedFolder.$filter({ sort: field }); vm.selectedFolder.$filter({ sort: field });
@ -192,6 +44,11 @@
return Mailbox.$query.sort == field; return Mailbox.$query.sort == field;
} }
function searchMode() {
vm.mode.search = true;
focus('search');
}
function cancelSearch() { function cancelSearch() {
vm.mode.search = false; vm.mode.search = false;
vm.selectedFolder.$filter().then(function() { vm.selectedFolder.$filter().then(function() {
@ -228,6 +85,183 @@
}); });
} }
} }
function selectMessage(message) {
if (Mailbox.$virtualMode)
$state.go('mail.account.virtualMailbox.message', {mailboxId: encodeUriFilter(message.$mailbox.path), messageId: message.uid});
else
$state.go('mail.account.mailbox.message', {messageId: message.uid});
}
function toggleMessageSelection($event, message) {
message.selected = !message.selected;
vm.mode.multiple += message.selected? 1 : -1;
$event.preventDefault();
$event.stopPropagation();
}
/**
* Batch operations
*/
function _currentMailboxes() {
if (Mailbox.$virtualMode)
return vm.selectedFolder.$mailboxes;
else
return [vm.selectedFolder];
}
function _unselectMessage(message, index) {
// Unselect current message and cleverly load the next message.
// This function must not be called in virtual mode.
var nextMessage, previousMessage, nextIndex = index;
vm.mode.multiple = vm.selectedFolder.$selectedCount();
if (message) {
// Select either the next or previous message
if (index > 0) {
nextIndex -= 1;
nextMessage = vm.selectedFolder.$messages[nextIndex];
}
if (index < vm.selectedFolder.$messages.length)
previousMessage = vm.selectedFolder.$messages[index];
if (nextMessage) {
if (nextMessage.isread && previousMessage && !previousMessage.isread) {
nextIndex = index;
nextMessage = previousMessage;
}
}
else if (previousMessage) {
nextIndex = index;
nextMessage = previousMessage;
}
if (nextMessage) {
vm.selectedFolder.$topIndex = nextIndex;
$state.go('mail.account.mailbox.message', { messageId: nextMessage.uid });
}
else {
$state.go('mail.account.mailbox');
}
}
else {
$timeout(function() {
console.warn('go to mailbox');
$state.go('mail.account.mailbox');
});
}
}
function confirmDeleteSelectedMessages() {
Dialog.confirm(l('Warning'),
l('Are you sure you want to delete the selected messages?'),
{ ok: l('Delete') })
.then(function() {
var deleteSelectedMessage = vm.selectedFolder.hasSelectedMessage();
var selectedMessages = vm.selectedFolder.$selectedMessages();
if (_.size(selectedMessages) > 0)
vm.selectedFolder.$deleteMessages(selectedMessages).then(function(index) {
if (Mailbox.$virtualMode) {
// When performing an advanced search, we refresh the view if the selected message
// was deleted, but only once all promises have completed.
if (deleteSelectedMessage)
$state.go('mail.account.virtualMailbox');
}
else {
// In normal mode, we immediately unselect the selected message.
_unselectMessage(deleteSelectedMessage, index);
}
});
});
}
function markOrUnMarkMessagesAsJunk() {
var moveSelectedMessage = vm.selectedFolder.hasSelectedMessage();
var selectedMessages = vm.selectedFolder.$selectedMessages();
if (_.size(selectedMessages) > 0)
vm.selectedFolder.$markOrUnMarkMessagesAsJunk(selectedMessages).then(function() {
var dstFolder = '/' + vm.account.id + '/folderINBOX';
if (vm.selectedFolder.type != 'junk') {
dstFolder = '/' + vm.account.$getMailboxByType('junk').id;
}
vm.selectedFolder.$moveMessages(selectedMessages, dstFolder).then(function(index) {
if (Mailbox.$virtualMode) {
// When performing an advanced search, we refresh the view if the selected message
// was deleted, but only once all promises have completed.
if (moveSelectedMessage)
$state.go('mail.account.virtualMailbox');
}
else {
// In normal mode, we immediately unselect the selected message.
_unselectMessage(moveSelectedMessage, index);
}
});
});
}
function copySelectedMessages(dstFolder) {
var selectedMessages = vm.selectedFolder.$selectedMessages();
if (_.size(selectedMessages) > 0)
vm.selectedFolder.$copyMessages(selectedMessages, '/' + dstFolder);
}
function moveSelectedMessages(dstFolder) {
var moveSelectedMessage = vm.selectedFolder.hasSelectedMessage();
var selectedMessages = vm.selectedFolder.$selectedMessages();
if (_.size(selectedMessages) > 0)
vm.selectedFolder.$moveMessages(selectedMessages, '/' + dstFolder).then(function(index) {
if (Mailbox.$virtualMode) {
// When performing an advanced search, we refresh the view if the selected message
// was moved, but only once all promises have completed.
if (moveSelectedMessage)
$state.go('mail.account.virtualMailbox');
}
else {
// In normal mode, we immediately unselect the selected message.
_unselectMessage(moveSelectedMessage, index);
}
});
}
function selectAll() {
var count = 0;
_.forEach(_currentMailboxes(), function(folder) {
var i = 0, length = folder.$messages.length;
for (; i < length; i++)
folder.$messages[i].selected = true;
count += length;
});
vm.mode.multiple = count;
}
function unselectMessages() {
_.forEach(_currentMailboxes(), function(folder) {
_.forEach(folder.$messages, function(message) {
message.selected = false;
});
});
vm.mode.multiple = 0;
}
function markSelectedMessagesAsFlagged() {
var selectedMessages = vm.selectedFolder.$selectedMessages();
if (_.size(selectedMessages) > 0)
vm.selectedFolder.$flagMessages(selectedMessages, '\\Flagged', 'add').then(function(messages) {
_.forEach(messages, function(message) {
message.isflagged = true;
});
});
}
function markSelectedMessagesAsUnread() {
var selectedMessages = vm.selectedFolder.$selectedMessages();
if (_.size(selectedMessages) > 0)
vm.selectedFolder.$flagMessages(selectedMessages, 'seen', 'remove').then(function(messages) {
_.forEach(messages, function(message) {
message.isread = false;
message.$mailbox.unseenCount++;
});
});
}
} }
angular angular

View File

@ -521,10 +521,19 @@
* @returns a promise of the HTTP operation * @returns a promise of the HTTP operation
*/ */
Message.prototype.$reload = function(options) { Message.prototype.$reload = function(options) {
var futureMessageData; var _this = this, futureMessageData;
if (options && options.useCache && this.$futureMessageData) if (options && options.useCache && this.$futureMessageData) {
if (!this.isread) {
Message.$$resource.fetch(this.$absolutePath(), 'markMessageRead').then(function() {
Message.$timeout(function() {
_this.isread = true;
_this.$mailbox.unseenCount--;
});
});
}
return this; return this;
}
futureMessageData = Message.$$resource.fetch(this.$absolutePath(options), 'view'); futureMessageData = Message.$$resource.fetch(this.$absolutePath(options), 'view');