diff --git a/lib/Contracts/IMailSearch.php b/lib/Contracts/IMailSearch.php
index 161aadaa40..1fd82feb65 100644
--- a/lib/Contracts/IMailSearch.php
+++ b/lib/Contracts/IMailSearch.php
@@ -34,6 +34,8 @@
use OCP\IUser;
interface IMailSearch {
+ public const ORDER_NEWEST_FIRST = 'DESC';
+ public const ORDER_OLDEST_FIRST = 'ASC';
/**
* @throws DoesNotExistException
* @throws ClientException
@@ -46,6 +48,7 @@ public function findMessage(Account $account,
/**
* @param Account $account
* @param Mailbox $mailbox
+ * @param string $sortOrder
* @param string|null $filter
* @param int|null $cursor
* @param int|null $limit
@@ -57,6 +60,7 @@ public function findMessage(Account $account,
*/
public function findMessages(Account $account,
Mailbox $mailbox,
+ string $sortOrder,
?string $filter,
?int $cursor,
?int $limit): array;
diff --git a/lib/Controller/AccountsController.php b/lib/Controller/AccountsController.php
index 0ddcefc168..5053758d72 100644
--- a/lib/Controller/AccountsController.php
+++ b/lib/Controller/AccountsController.php
@@ -461,6 +461,7 @@ public function draft(int $id,
$draftsMailbox,
Horde_Imap_Client::SYNC_NEWMSGSUIDS,
[],
+ null,
false
);
return new JSONResponse([
diff --git a/lib/Controller/MailboxesController.php b/lib/Controller/MailboxesController.php
index ede4181c63..205a5145ae 100644
--- a/lib/Controller/MailboxesController.php
+++ b/lib/Controller/MailboxesController.php
@@ -27,6 +27,7 @@
use Horde_Imap_Client;
use OCA\Mail\Contracts\IMailManager;
+use OCA\Mail\Contracts\IMailSearch;
use OCA\Mail\Exception\ClientException;
use OCA\Mail\Exception\IncompleteSyncException;
use OCA\Mail\Exception\MailboxNotCachedException;
@@ -145,9 +146,10 @@ public function patch(int $id,
* @throws ServiceException
*/
#[TrapError]
- public function sync(int $id, array $ids = [], bool $init = false, string $query = null): JSONResponse {
+ public function sync(int $id, array $ids = [], ?int $lastMessageTimestamp, bool $init = false, string $sortOrder = 'newest', string $query = null): JSONResponse {
$mailbox = $this->mailManager->getMailbox($this->currentUserId, $id);
$account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId());
+ $order = $sortOrder === 'newest' ? IMailSearch::ORDER_NEWEST_FIRST: IMailSearch::ORDER_OLDEST_FIRST;
try {
$syncResponse = $this->syncService->syncMailbox(
@@ -157,7 +159,9 @@ public function sync(int $id, array $ids = [], bool $init = false, string $query
array_map(static function ($id) {
return (int)$id;
}, $ids),
+ $lastMessageTimestamp,
!$init,
+ $order,
$query
);
} catch (MailboxNotCachedException $e) {
diff --git a/lib/Controller/MessagesController.php b/lib/Controller/MessagesController.php
index 66ff383057..ef82a9db62 100755
--- a/lib/Controller/MessagesController.php
+++ b/lib/Controller/MessagesController.php
@@ -39,6 +39,7 @@
use OCA\Mail\Contracts\IMailSearch;
use OCA\Mail\Contracts\IMailTransmission;
use OCA\Mail\Contracts\ITrustedSenderService;
+use OCA\Mail\Contracts\IUserPreferences;
use OCA\Mail\Db\Message;
use OCA\Mail\Exception\ClientException;
use OCA\Mail\Exception\ServiceException;
@@ -87,6 +88,7 @@ class MessagesController extends Controller {
private SmimeService $smimeService;
private IMAPClientFactory $clientFactory;
private IDkimService $dkimService;
+ private IUserPreferences $preferences;
private SnoozeService $snoozeService;
public function __construct(string $appName,
@@ -107,6 +109,7 @@ public function __construct(string $appName,
SmimeService $smimeService,
IMAPClientFactory $clientFactory,
IDkimService $dkimService,
+ IUserPreferences $preferences,
SnoozeService $snoozeService) {
parent::__construct($appName, $request);
$this->accountService = $accountService;
@@ -125,6 +128,7 @@ public function __construct(string $appName,
$this->smimeService = $smimeService;
$this->clientFactory = $clientFactory;
$this->dkimService = $dkimService;
+ $this->preferences = $preferences;
$this->snoozeService = $snoozeService;
}
@@ -153,12 +157,14 @@ public function index(int $mailboxId,
return new JSONResponse([], Http::STATUS_FORBIDDEN);
}
- $this->logger->debug("loading messages of folder <$mailboxId>");
+ $this->logger->debug("loading messages of mailbox <$mailboxId>");
+ $order = $this->preferences->getPreference($this->currentUserId, 'sort-order', 'newest') === 'newest' ? 'DESC': 'ASC';
return new JSONResponse(
$this->mailSearch->findMessages(
$account,
$mailbox,
+ $order,
$filter === '' ? null : $filter,
$cursor,
$limit
diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php
index 7c470ae868..8eab8b5480 100644
--- a/lib/Controller/PageController.php
+++ b/lib/Controller/PageController.php
@@ -170,6 +170,11 @@ public function index(): TemplateResponse {
$this->tagMapper->getAllTagsForUser($this->currentUserId)
);
+ $this->initialStateService->provideInitialState(
+ 'sort-order',
+ $this->preferences->getPreference($this->currentUserId, 'sort-order', 'newest')
+ );
+
try {
$password = $this->credentialStore->getLoginCredentials()->getPassword();
$passwordIsUnavailable = $password === null || $password === '';
diff --git a/lib/Db/MessageMapper.php b/lib/Db/MessageMapper.php
index 4336bf6484..25ee162132 100644
--- a/lib/Db/MessageMapper.php
+++ b/lib/Db/MessageMapper.php
@@ -29,6 +29,7 @@
use OCA\Mail\Account;
use OCA\Mail\Address;
use OCA\Mail\AddressList;
+use OCA\Mail\Contracts\IMailSearch;
use OCA\Mail\IMAP\Threading\DatabaseMessage;
use OCA\Mail\Service\Search\Flag;
use OCA\Mail\Service\Search\FlagExpression;
@@ -700,7 +701,7 @@ public function findByMessageId(Account $account, string $messageId): array {
*
* @return int[]
*/
- public function findIdsByQuery(Mailbox $mailbox, SearchQuery $query, ?int $limit, array $uids = null): array {
+ public function findIdsByQuery(Mailbox $mailbox, SearchQuery $query, string $sortOrder, ?int $limit, array $uids = null): array {
$qb = $this->db->getQueryBuilder();
if ($this->needDistinct($query)) {
@@ -828,10 +829,14 @@ public function findIdsByQuery(Mailbox $mailbox, SearchQuery $query, ?int $limit
);
}
- if ($query->getCursor() !== null) {
+ if ($query->getCursor() !== null && $sortOrder === IMailSearch::ORDER_NEWEST_FIRST) {
$select->andWhere(
$qb->expr()->lt('m.sent_at', $qb->createNamedParameter($query->getCursor(), IQueryBuilder::PARAM_INT))
);
+ } elseif ($query->getCursor() !== null && $sortOrder === IMailSearch::ORDER_OLDEST_FIRST) {
+ $select->andWhere(
+ $qb->expr()->gt('m.sent_at', $qb->createNamedParameter($query->getCursor(), IQueryBuilder::PARAM_INT))
+ );
}
// createParameter
@@ -861,7 +866,11 @@ public function findIdsByQuery(Mailbox $mailbox, SearchQuery $query, ?int $limit
$select->andWhere($qb->expr()->isNull('m2.id'));
- $select->orderBy('m.sent_at', 'desc');
+ if ($sortOrder === 'ASC') {
+ $select->orderBy('sent_at', $sortOrder);
+ } else {
+ $select->orderBy('sent_at', 'DESC');
+ }
if ($limit !== null) {
$select->setMaxResults($limit);
@@ -876,9 +885,10 @@ public function findIdsByQuery(Mailbox $mailbox, SearchQuery $query, ?int $limit
}, array_chunk($uids, 1000));
}
- return array_map(static function (Message $message) {
+ $result = array_map(static function (Message $message) {
return $message->getId();
}, $this->findEntities($select));
+ return $result;
}
public function findIdsGloballyByQuery(IUser $user, SearchQuery $query, ?int $limit, array $uids = null): array {
@@ -1115,21 +1125,21 @@ public function findByMailboxAndIds(Mailbox $mailbox, string $userId, array $ids
/**
* @param string $userId
* @param int[] $ids
+ * @param string $sortOrder
*
* @return Message[]
*/
- public function findByIds(string $userId, array $ids): array {
+ public function findByIds(string $userId, array $ids, string $sortOrder): array {
if ($ids === []) {
return [];
}
-
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->in('id', $qb->createParameter('ids'))
)
- ->orderBy('sent_at', 'desc');
+ ->orderBy('sent_at', $sortOrder);
$results = [];
foreach (array_chunk($ids, 1000) as $chunk) {
@@ -1215,14 +1225,19 @@ public function findRelatedData(array $messages, string $userId): array {
/**
* @param Mailbox $mailbox
* @param array $ids
+ * @param int|null $lastMessageTimestamp
+ * @param IMailSearch::ORDER_* $sortOrder
+ *
* @return int[]
*/
- public function findNewIds(Mailbox $mailbox, array $ids): array {
+ public function findNewIds(Mailbox $mailbox, array $ids, ?int $lastMessageTimestamp, string $sortOrder): array {
$select = $this->db->getQueryBuilder();
$subSelect = $this->db->getQueryBuilder();
$subSelect
- ->select($subSelect->func()->min('sent_at'))
+ ->select($sortOrder === IMailSearch::ORDER_NEWEST_FIRST ?
+ $subSelect->func()->min('sent_at') :
+ $subSelect->func()->max('sent_at'))
->from($this->getTableName())
->where(
$subSelect->expr()->eq('mailbox_id', $select->createNamedParameter($mailbox->getId(), IQueryBuilder::PARAM_INT)),
@@ -1234,20 +1249,31 @@ public function findNewIds(Mailbox $mailbox, array $ids): array {
$selfJoin = $select->expr()->andX(
$select->expr()->eq('m.mailbox_id', 'm2.mailbox_id', IQueryBuilder::PARAM_INT),
$select->expr()->eq('m.thread_root_id', 'm2.thread_root_id', IQueryBuilder::PARAM_INT),
- $select->expr()->lt('m.sent_at', 'm2.sent_at', IQueryBuilder::PARAM_INT)
+ $sortOrder === IMailSearch::ORDER_NEWEST_FIRST ?
+ $select->expr()->lt('m.sent_at', 'm2.sent_at', IQueryBuilder::PARAM_INT) :
+ $select->expr()->gt('m.sent_at', 'm2.sent_at', IQueryBuilder::PARAM_INT)
);
+ $wheres = [$select->expr()->eq('m.mailbox_id', $select->createNamedParameter($mailbox->getId(), IQueryBuilder::PARAM_INT)),
+ $select->expr()->andX($subSelect->expr()->notIn('m.id', $select->createParameter('ids'), IQueryBuilder::PARAM_INT_ARRAY)),
+ $select->expr()->isNull('m2.id'),
+ ];
+ if ($sortOrder === IMailSearch::ORDER_NEWEST_FIRST) {
+ $wheres[] = $select->expr()->gt('m.sent_at', $select->createFunction('(' . $subSelect->getSQL() . ')'), IQueryBuilder::PARAM_INT);
+ } else {
+ $wheres[] = $select->expr()->lt('m.sent_at', $select->createFunction('(' . $subSelect->getSQL() . ')'), IQueryBuilder::PARAM_INT);
+ }
+
+ if ($lastMessageTimestamp !== null && $sortOrder === IMailSearch::ORDER_OLDEST_FIRST) {
+ // Don't consider old "new messages" as new when their UID has already been seen before
+ $wheres[] = $select->expr()->lt('m.sent_at', $select->createNamedParameter($lastMessageTimestamp, IQueryBuilder::PARAM_INT));
+ }
$select
- ->select('m.id')
+ ->select(['m.id', 'm.sent_at'])
->from($this->getTableName(), 'm')
->leftJoin('m', $this->getTableName(), 'm2', $selfJoin)
- ->where(
- $select->expr()->eq('m.mailbox_id', $select->createNamedParameter($mailbox->getId(), IQueryBuilder::PARAM_INT)),
- $select->expr()->andX($subSelect->expr()->notIn('m.id', $select->createParameter('ids'), IQueryBuilder::PARAM_INT_ARRAY)),
- $select->expr()->isNull('m2.id'),
- $select->expr()->gt('m.sent_at', $select->createFunction('(' . $subSelect->getSQL() . ')'), IQueryBuilder::PARAM_INT)
- )
- ->orderBy('m.sent_at', 'desc');
+ ->where(...$wheres)
+ ->orderBy('m.sent_at', $sortOrder === IMailSearch::ORDER_NEWEST_FIRST ? 'desc' : 'asc');
$results = [];
foreach (array_chunk($ids, 1000) as $chunk) {
diff --git a/lib/IMAP/Sync/Synchronizer.php b/lib/IMAP/Sync/Synchronizer.php
index d179722fbb..b9011a2101 100644
--- a/lib/IMAP/Sync/Synchronizer.php
+++ b/lib/IMAP/Sync/Synchronizer.php
@@ -101,7 +101,7 @@ public function sync(Horde_Imap_Client_Base $imapClient,
$changedMessages = $this->messageMapper->findByIds($imapClient, $request->getMailbox(), $changedUids, $userId);
$vanishedMessageUids = $vanishedUids;
- return new Response($newMessages, $changedMessages, $vanishedMessageUids);
+ return new Response($newMessages, $changedMessages, $vanishedMessageUids, null);
}
/**
diff --git a/lib/Service/Search/MailSearch.php b/lib/Service/Search/MailSearch.php
index c4068efaee..5be595b6e3 100644
--- a/lib/Service/Search/MailSearch.php
+++ b/lib/Service/Search/MailSearch.php
@@ -92,6 +92,7 @@ public function findMessage(Account $account,
/**
* @param Account $account
* @param Mailbox $mailbox
+ * @param string $sortOrder
* @param string|null $filter
* @param int|null $cursor
* @param int|null $limit
@@ -103,6 +104,7 @@ public function findMessage(Account $account,
*/
public function findMessages(Account $account,
Mailbox $mailbox,
+ string $sortOrder,
?string $filter,
?int $cursor,
?int $limit): array {
@@ -130,7 +132,8 @@ public function findMessages(Account $account,
$account,
$mailbox,
$this->messageMapper->findByIds($account->getUserId(),
- $this->getIdsLocally($account, $mailbox, $query, $limit)
+ $this->getIdsLocally($account, $mailbox, $query, $sortOrder, $limit),
+ $sortOrder,
)
);
}
@@ -155,7 +158,8 @@ public function findMessagesGlobally(IUser $user,
}
return $this->messageMapper->findByIds($user->getUID(),
- $this->getIdsGlobally($user, $query, $limit)
+ $this->getIdsGlobally($user, $query, $limit),
+ 'DESC'
);
}
@@ -164,9 +168,9 @@ public function findMessagesGlobally(IUser $user,
*
* @throws ServiceException
*/
- private function getIdsLocally(Account $account, Mailbox $mailbox, SearchQuery $query, ?int $limit): array {
+ private function getIdsLocally(Account $account, Mailbox $mailbox, SearchQuery $query, string $sortOrder, ?int $limit): array {
if (empty($query->getBodies())) {
- return $this->messageMapper->findIdsByQuery($mailbox, $query, $limit);
+ return $this->messageMapper->findIdsByQuery($mailbox, $query, $sortOrder, $limit);
}
$fromImap = $this->imapSearchProvider->findMatches(
@@ -174,7 +178,7 @@ private function getIdsLocally(Account $account, Mailbox $mailbox, SearchQuery $
$mailbox,
$query
);
- return $this->messageMapper->findIdsByQuery($mailbox, $query, $limit, $fromImap);
+ return $this->messageMapper->findIdsByQuery($mailbox, $query, $sortOrder, $limit, $fromImap);
}
/**
diff --git a/lib/Service/Sync/SyncService.php b/lib/Service/Sync/SyncService.php
index 01b465abb4..dd18b2dc27 100644
--- a/lib/Service/Sync/SyncService.php
+++ b/lib/Service/Sync/SyncService.php
@@ -26,6 +26,7 @@
namespace OCA\Mail\Service\Sync;
use OCA\Mail\Account;
+use OCA\Mail\Contracts\IMailSearch;
use OCA\Mail\Db\Mailbox;
use OCA\Mail\Db\MailboxMapper;
use OCA\Mail\Db\Message;
@@ -111,7 +112,9 @@ public function syncMailbox(Account $account,
Mailbox $mailbox,
int $criteria,
array $knownIds = null,
+ ?int $lastMessageTimestamp,
bool $partialOnly,
+ string $sortOrder = IMailSearch::ORDER_NEWEST_FIRST,
string $filter = null): Response {
if ($partialOnly && !$mailbox->isCached()) {
throw MailboxNotCachedException::from($mailbox);
@@ -133,6 +136,8 @@ public function syncMailbox(Account $account,
$account,
$mailbox,
$knownIds ?? [],
+ $lastMessageTimestamp,
+ $sortOrder,
$query
);
}
@@ -150,24 +155,26 @@ public function syncMailbox(Account $account,
private function getDatabaseSyncChanges(Account $account,
Mailbox $mailbox,
array $knownIds,
+ ?int $lastMessageTimestamp,
+ string $sortOrder,
?SearchQuery $query): Response {
if ($knownIds === []) {
$newIds = $this->messageMapper->findAllIds($mailbox);
} else {
- $newIds = $this->messageMapper->findNewIds($mailbox, $knownIds);
+ $newIds = $this->messageMapper->findNewIds($mailbox, $knownIds, $lastMessageTimestamp, $sortOrder);
}
-
+ $order = $sortOrder === 'oldest' ? IMailSearch::ORDER_OLDEST_FIRST : IMailSearch::ORDER_NEWEST_FIRST;
if ($query !== null) {
// Filter new messages to those that also match the current filter
$newUids = $this->messageMapper->findUidsForIds($mailbox, $newIds);
- $newIds = $this->messageMapper->findIdsByQuery($mailbox, $query, null, $newUids);
+ $newIds = $this->messageMapper->findIdsByQuery($mailbox, $query, $order, null, $newUids);
}
$new = $this->messageMapper->findByMailboxAndIds($mailbox, $account->getUserId(), $newIds);
// TODO: $changed = $this->messageMapper->findChanged($account, $mailbox, $uids);
if ($query !== null) {
$changedUids = $this->messageMapper->findUidsForIds($mailbox, $knownIds);
- $changedIds = $this->messageMapper->findIdsByQuery($mailbox, $query, null, $changedUids);
+ $changedIds = $this->messageMapper->findIdsByQuery($mailbox, $query, $order, null, $changedUids);
} else {
$changedIds = $knownIds;
}
diff --git a/src/components/AppSettingsMenu.vue b/src/components/AppSettingsMenu.vue
index 4d28f7a0ff..274c3c1f6e 100755
--- a/src/components/AppSettingsMenu.vue
+++ b/src/components/AppSettingsMenu.vue
@@ -99,6 +99,29 @@
{{ t('mail', 'Looking for a way to encrypt your emails?') }}
@@ -116,7 +139,7 @@
import { generateUrl } from '@nextcloud/router'
import { showError } from '@nextcloud/dialogs'
-import { NcButton as ButtonVue, NcLoadingIcon as IconLoading } from '@nextcloud/vue'
+import { NcButton as ButtonVue, NcLoadingIcon as IconLoading, NcCheckboxRadioSwitch as CheckboxRadioSwitch } from '@nextcloud/vue'
import IconInfo from 'vue-material-design-icons/Information'
import IconAdd from 'vue-material-design-icons/Plus'
@@ -137,6 +160,7 @@ export default {
IconLoading,
IconLock,
SmimeCertificateModal,
+ CheckboxRadioSwitch,
},
data() {
return {
@@ -152,6 +176,7 @@ export default {
autoTaggingText: t('mail', 'Automatically classify importance of new email'),
toggleAutoTagging: false,
displaySmimeCertificateModal: false,
+ sortOrder: 'newest',
}
},
computed: {
@@ -171,6 +196,9 @@ export default {
return this.$store.getters.getPreference('allow-new-accounts', true)
},
},
+ mounted() {
+ this.sortOrder = this.$store.getters.getPreference('sort-order', 'newest')
+ },
methods: {
onToggleButtonReplies(e) {
this.loadingReplySettings = true
@@ -211,6 +239,23 @@ export default {
this.loadingOptOutSettings = false
})
},
+ async onSortByDate(e) {
+ const previousValue = this.sortOrder
+ try {
+ this.sortOrder = e
+ await this.$store
+ .dispatch('savePreference', {
+ key: 'sort-order',
+ value: e,
+ })
+ this.$store.commit('removeAllEnvelopes')
+
+ } catch (error) {
+ Logger.error('could not save preferences', { error })
+ this.sortOrder = previousValue
+ showError(t('mail', 'Could not update preference'))
+ }
+ },
async onToggleAutoTagging(e) {
this.toggleAutoTagging = true
@@ -310,4 +355,21 @@ p.app-settings {
}
}
}
+.material-design-icon {
+ &.lock-icon {
+ margin-right: 10px;
+ }
+
+}
+.section-title {
+ margin-top: 20px;
+ margin-bottom: 10px;
+}
+.sorting {
+ display: flex;
+ width: 100%;
+ &__switch{
+ width: 50%;
+ }
+}
diff --git a/src/components/EnvelopeList.vue b/src/components/EnvelopeList.vue
index feebe04c29..2185249cbb 100644
--- a/src/components/EnvelopeList.vue
+++ b/src/components/EnvelopeList.vue
@@ -236,7 +236,7 @@