diff --git a/lib/Contracts/IMailManager.php b/lib/Contracts/IMailManager.php index 701d7ee814..27ae10c08c 100644 --- a/lib/Contracts/IMailManager.php +++ b/lib/Contracts/IMailManager.php @@ -109,7 +109,7 @@ public function getImapMessage(Horde_Imap_Client_Socket $client, * * @return Message[] */ - public function getThread(Account $account, string $threadRootId): array; + public function getThread(Account $account, string $threadRootId, string $sortOrder = IMailSearch::ORDER_NEWEST_FIRST): array; /** * @param Account $sourceAccount diff --git a/lib/Contracts/IMailSearch.php b/lib/Contracts/IMailSearch.php index 4bee40bca8..db8c8c44ee 100644 --- a/lib/Contracts/IMailSearch.php +++ b/lib/Contracts/IMailSearch.php @@ -42,7 +42,7 @@ public function findMessage(Account $account, * @param string|null $userId * @param string|null $view * - * @return Message[] + * @return Message[][] * * @throws ClientException * @throws ServiceException @@ -59,7 +59,7 @@ public function findMessages(Account $account, /** * Run a search through all mailboxes of a user. * - * @return Message[] + * @return Message[][] * * @throws ClientException * @throws ServiceException diff --git a/lib/Db/MessageMapper.php b/lib/Db/MessageMapper.php index 62797e4e9a..d3e5a4b502 100644 --- a/lib/Db/MessageMapper.php +++ b/lib/Db/MessageMapper.php @@ -746,10 +746,11 @@ public function deleteByUid(Mailbox $mailbox, int ...$uids): void { /** * @param Account $account * @param string $threadRootId + * @param string $sortOrder * * @return Message[] */ - public function findThread(Account $account, string $threadRootId): array { + public function findThread(Account $account, string $threadRootId, string $sortOrder): array { $qb = $this->db->getQueryBuilder(); $qb->select('messages.*') ->from($this->getTableName(), 'messages') @@ -758,7 +759,7 @@ public function findThread(Account $account, string $threadRootId): array { $qb->expr()->eq('mailboxes.account_id', $qb->createNamedParameter($account->getId(), IQueryBuilder::PARAM_INT)), $qb->expr()->eq('messages.thread_root_id', $qb->createNamedParameter($threadRootId, IQueryBuilder::PARAM_STR), IQueryBuilder::PARAM_STR) ) - ->orderBy('messages.sent_at', 'desc'); + ->orderBy('messages.sent_at', $sortOrder); return $this->findRelatedData($this->findEntities($qb), $account->getUserId()); } @@ -1223,10 +1224,11 @@ public function findByUids(Mailbox $mailbox, array $uids): array { * @param Mailbox $mailbox * @param string $userId * @param int[] $ids + * @param string $sortOrder * * @return Message[] */ - public function findByMailboxAndIds(Mailbox $mailbox, string $userId, array $ids): array { + public function findByMailboxAndIds(Mailbox $mailbox, string $userId, array $ids, string $sortOrder): array { if ($ids === []) { return []; } @@ -1238,7 +1240,7 @@ public function findByMailboxAndIds(Mailbox $mailbox, string $userId, array $ids $qb->expr()->eq('mailbox_id', $qb->createNamedParameter($mailbox->getId()), IQueryBuilder::PARAM_INT), $qb->expr()->in('id', $qb->createParameter('ids')) ) - ->orderBy('sent_at', 'desc'); + ->orderBy('sent_at', $sortOrder); $results = []; foreach (array_chunk($ids, 1000) as $chunk) { @@ -1248,6 +1250,71 @@ public function findByMailboxAndIds(Mailbox $mailbox, string $userId, array $ids return array_merge([], ...$results); } + /** + * @param Account $account + * @param Mailbox $mailbox + * @param string $userId + * @param int[] $ids + * @param string $sortOrder + * @param bool $threadingEnabled + * + * @return Message[][] + */ + public function findMessageListsByMailboxAndIds(Account $account, Mailbox $mailbox, string $userId, array $ids, string $sortOrder, bool $threadingEnabled = false): array { + if ($ids === []) { + return []; + } + + $base = $this->findByMailboxAndIds($mailbox, $userId, $ids, $sortOrder); + if ($threadingEnabled === false) { + return array_map(static fn (Message $m) => [$m], $base); + } + + $threadRoots = array_unique( + array_map(static fn (Message $m) => $m->getThreadRootId(), $base) + ); + + $allThreadMsgs = []; + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from($this->getTableName()) + ->where( + $qb->expr()->eq('mailbox_id', $qb->createNamedParameter($mailbox->getId(), IQueryBuilder::PARAM_INT)), + $qb->expr()->in('thread_root_id', $qb->createParameter('roots')), + $qb->expr()->notIn('id', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)) + ) + ->orderBy('sent_at', $sortOrder); + foreach (array_chunk($threadRoots, 1000) as $chunk) { + $qb->setParameter('roots', $chunk, IQueryBuilder::PARAM_STR_ARRAY); + $allThreadMsgs[] = $this->findEntities($qb); + } + $allThreadMsgs = array_merge($base, ...$allThreadMsgs); + + $enriched = $this->findRelatedData(array_values($allThreadMsgs), $userId); + + $groups = []; + foreach ($enriched as $m) { + $root = $m->getThreadRootId(); + $groups[$root][] = $m; + } + + $orderKeys = []; + foreach ($base as $m) { + $key = $m->getThreadRootId(); + if (!isset($orderKeys[$key])) { + $orderKeys[$key] = true; + } + } + + $out = []; + foreach (array_keys($orderKeys) as $k) { + if (isset($groups[$k])) { + $out[] = $groups[$k]; + } + } + return $out; + } + /** * @param string $userId * @param int[] $ids @@ -1275,6 +1342,70 @@ public function findByIds(string $userId, array $ids, string $sortOrder): array return array_merge([], ...$results); } + + /** + * @param Account $account + * @param string $userId + * @param int[] $ids + * @param string $sortOrder + * + * @return Message[][] + */ + public function findMessageListsByIds(Account $account, string $userId, array $ids, string $sortOrder, bool $threadingEnabled = false): array { + if ($ids === []) { + return []; + } + + $base = $this->findByIds($userId, $ids, $sortOrder); + + if ($threadingEnabled === false) { + return array_map(static fn (Message $m) => [$m], $base); + } + + $threadRoots = array_unique( + array_map(static fn (Message $m) => $m->getThreadRootId(), $base) + ); + + $allThreadMsgs = []; + $qb = $this->db->getQueryBuilder(); + $qb->select('m.*') + ->from($this->getTableName(), 'm') + ->join('m', 'mail_mailboxes', 'mb', $qb->expr()->eq('m.mailbox_id', 'mb.id', IQueryBuilder::PARAM_INT)) + ->where( + $qb->expr()->eq('mb.account_id', $qb->createNamedParameter($account->getId(), IQueryBuilder::PARAM_INT)), + $qb->expr()->in('m.thread_root_id', $qb->createParameter('roots')), + $qb->expr()->notIn('m.id', $qb->createNamedParameter($ids, IQueryBuilder::PARAM_INT_ARRAY)) + ) + ->orderBy('m.sent_at', $sortOrder); + foreach (array_chunk($threadRoots, 1000) as $chunk) { + $qb->setParameter('roots', $chunk, IQueryBuilder::PARAM_STR_ARRAY); + $allThreadMsgs[] = $this->findEntities($qb); + } + $allThreadMsgs = array_merge($base, ...$allThreadMsgs); + + $enriched = $this->findRelatedData(array_values($allThreadMsgs), $userId); + $groups = []; + foreach ($enriched as $m) { + $root = $m->getThreadRootId(); + $groups[$root][] = $m; + } + $orderKeys = []; + foreach ($base as $m) { + $key = $m->getThreadRootId(); + if (!isset($orderKeys[$key])) { + $orderKeys[$key] = true; + } + } + + $out = []; + foreach (array_keys($orderKeys) as $k) { + if (isset($groups[$k])) { + $out[] = $groups[$k]; + } + } + return $out; + } + /** * @param Message[] $messages * diff --git a/lib/IMAP/PreviewEnhancer.php b/lib/IMAP/PreviewEnhancer.php index eadc094cac..89f9f7c999 100644 --- a/lib/IMAP/PreviewEnhancer.php +++ b/lib/IMAP/PreviewEnhancer.php @@ -52,9 +52,9 @@ public function __construct(IMAPClientFactory $clientFactory, } /** - * @param Message[] $messages + * @param Message[][] $messages * - * @return Message[] + * @return Message[][] */ public function process(Account $account, Mailbox $mailbox, array $messages, bool $preLoadAvatars = false, ?string $userId = null): array { $needAnalyze = array_reduce($messages, static function (array $carry, Message $message) { diff --git a/lib/Service/MailManager.php b/lib/Service/MailManager.php index 0f90e7f898..382fc407ad 100644 --- a/lib/Service/MailManager.php +++ b/lib/Service/MailManager.php @@ -17,6 +17,7 @@ use OCA\Mail\Account; use OCA\Mail\Attachment; use OCA\Mail\Contracts\IMailManager; +use OCA\Mail\Contracts\IMailSearch; use OCA\Mail\Db\Mailbox; use OCA\Mail\Db\MailboxMapper; use OCA\Mail\Db\Message; @@ -237,8 +238,8 @@ public function getImapMessagesForScheduleProcessing(Account $account, } #[\Override] - public function getThread(Account $account, string $threadRootId): array { - return $this->dbMessageMapper->findThread($account, $threadRootId); + public function getThread(Account $account, string $threadRootId, string $sortOrder = IMailSearch::ORDER_NEWEST_FIRST): array { + return $this->dbMessageMapper->findThread($account, $threadRootId, $sortOrder); } #[\Override] diff --git a/lib/Service/Search/MailSearch.php b/lib/Service/Search/MailSearch.php index 16f8ada442..aee950a72b 100644 --- a/lib/Service/Search/MailSearch.php +++ b/lib/Service/Search/MailSearch.php @@ -77,7 +77,7 @@ public function findMessage(Account $account, * @param int|null $limit * @param string|null $view * - * @return Message[] + * @return Message[][] * * @throws ClientException * @throws ServiceException @@ -102,8 +102,9 @@ public function findMessages(Account $account, if ($cursor !== null) { $query->setCursor($cursor); } + $threadingEnabled = $view === self::VIEW_THREADED; if ($view !== null) { - $query->setThreaded($view === self::VIEW_THREADED); + $query->setThreaded($threadingEnabled); } // In flagged we don't want anything but flagged messages if ($mailbox->isSpecialUse(Horde_Imap_Client::SPECIALUSE_FLAGGED)) { @@ -113,17 +114,23 @@ public function findMessages(Account $account, if (!$mailbox->isSpecialUse(Horde_Imap_Client::SPECIALUSE_TRASH)) { $query->addFlag(Flag::not(Flag::DELETED)); } - - return $this->previewEnhancer->process( - $account, - $mailbox, - $this->messageMapper->findByIds($account->getUserId(), - $this->getIdsLocally($account, $mailbox, $query, $sortOrder, $limit), - $sortOrder, - ), - true, - $userId + $messages = $this->messageMapper->findMessageListsByIds($account, $account->getUserId(), + $this->getIdsLocally($account, $mailbox, $query, $sortOrder, $limit), + $sortOrder, + $threadingEnabled ); + $processedMessages = []; + foreach ($messages as $messageList) { + $processedMessages[] = $this->previewEnhancer->process( + $account, + $mailbox, + $messageList, + true, + $userId + ); + } + + return $processedMessages; } /** diff --git a/lib/Service/Sync/SyncService.php b/lib/Service/Sync/SyncService.php index 0cff18572e..3ac61b04a9 100644 --- a/lib/Service/Sync/SyncService.php +++ b/lib/Service/Sync/SyncService.php @@ -24,6 +24,7 @@ use OCA\Mail\IMAP\Sync\Response; use OCA\Mail\Service\Search\FilterStringParser; use OCA\Mail\Service\Search\SearchQuery; +use OCP\IAppConfig; use Psr\Log\LoggerInterface; use function array_diff; use function array_map; @@ -50,6 +51,9 @@ class SyncService { /** @var MailboxSync */ private $mailboxSync; + /** @var IAppConfig */ + private $config; + public function __construct( IMAPClientFactory $clientFactory, ImapToDbSynchronizer $synchronizer, @@ -57,7 +61,9 @@ public function __construct( MessageMapper $messageMapper, PreviewEnhancer $previewEnhancer, LoggerInterface $logger, - MailboxSync $mailboxSync) { + MailboxSync $mailboxSync, + IAppConfig $config, + ) { $this->clientFactory = $clientFactory; $this->synchronizer = $synchronizer; $this->filterStringParser = $filterStringParser; @@ -65,6 +71,7 @@ public function __construct( $this->previewEnhancer = $previewEnhancer; $this->logger = $logger; $this->mailboxSync = $mailboxSync; + $this->config = $config; } /** @@ -129,6 +136,7 @@ public function syncMailbox(Account $account, $this->mailboxSync->syncStats($client, $mailbox); + $threadingEnabled = $this->config->getValueString('mail', 'layout-message-view', 'threaded') === 'threaded'; $client->logout(); $query = $filter === null ? null : $this->filterStringParser->parse($filter); @@ -138,7 +146,8 @@ public function syncMailbox(Account $account, $knownIds ?? [], $lastMessageTimestamp, $sortOrder, - $query + $query, + $threadingEnabled ); } @@ -147,6 +156,7 @@ public function syncMailbox(Account $account, * @param Mailbox $mailbox * @param int[] $knownIds * @param SearchQuery $query + * @param bool $threadingEnabled * * @return Response * @todo does not work with text token search queries @@ -157,7 +167,8 @@ private function getDatabaseSyncChanges(Account $account, array $knownIds, ?int $lastMessageTimestamp, string $sortOrder, - ?SearchQuery $query): Response { + ?SearchQuery $query, + bool $threadingEnabled): Response { if ($knownIds === []) { $newIds = $this->messageMapper->findAllIds($mailbox); } else { @@ -169,7 +180,13 @@ private function getDatabaseSyncChanges(Account $account, $newUids = $this->messageMapper->findUidsForIds($mailbox, $newIds); $newIds = $this->messageMapper->findIdsByQuery($mailbox, $query, $order, null, $newUids); } - $new = $this->messageMapper->findByMailboxAndIds($mailbox, $account->getUserId(), $newIds); + + $new = $this->messageMapper->findMessageListsByMailboxAndIds($account, $mailbox, $account->getUserId(), $newIds, $sortOrder, $threadingEnabled); + + $newMessages = []; + foreach ($new as $messageList) { + $newMessages[] = $this->previewEnhancer->process($account, $mailbox, $messageList); + } // TODO: $changed = $this->messageMapper->findChanged($account, $mailbox, $uids); if ($query !== null) { @@ -178,13 +195,13 @@ private function getDatabaseSyncChanges(Account $account, } else { $changedIds = $knownIds; } - $changed = $this->messageMapper->findByMailboxAndIds($mailbox, $account->getUserId(), $changedIds); + $changed = $this->messageMapper->findByMailboxAndIds($mailbox, $account->getUserId(), $changedIds, $sortOrder); $stillKnownIds = array_map(static fn (Message $msg) => $msg->getId(), $changed); $vanished = array_values(array_diff($knownIds, $stillKnownIds)); return new Response( - $this->previewEnhancer->process($account, $mailbox, $new), + $newMessages, $changed, $vanished, $mailbox->getStats() diff --git a/src/components/Mailbox.vue b/src/components/Mailbox.vue index e9afe18962..2da147570a 100644 --- a/src/components/Mailbox.vue +++ b/src/components/Mailbox.vue @@ -285,6 +285,7 @@ export default { } }, default: (error) => { + console.error(error) logger.error(`Could not fetch envelopes of folder ${this.mailbox.databaseId} (${this.searchQuery})`, { error }) this.loadingEnvelopes = false this.error = error diff --git a/src/service/MessageService.js b/src/service/MessageService.js index 1eed0cb465..35df1ce503 100644 --- a/src/service/MessageService.js +++ b/src/service/MessageService.js @@ -61,7 +61,7 @@ export function fetchEnvelopes(accountId, mailboxId, query, cursor, limit, sort, params, }) .then((resp) => resp.data) - .then((envelopes) => envelopes.map(amendEnvelopeWithIds(accountId))) + .then((data) => data.map((envelopes) => envelopes.map(amendEnvelopeWithIds(accountId)))) .catch((error) => { throw convertAxiosError(error) }) @@ -94,7 +94,7 @@ export async function syncEnvelopes(accountId, id, ids, lastMessageTimestamp, qu const amend = amendEnvelopeWithIds(accountId) return { - newMessages: response.data.newMessages.map(amend), + newMessages: response.data.newMessages.map((envelopes) => envelopes.map(amend)), changedMessages: response.data.changedMessages.map(amend), vanishedMessages: response.data.vanishedMessages, stats: response.data.stats, diff --git a/src/store/mainStore.js b/src/store/mainStore.js index 8b6f03d49c..4acc015a9a 100644 --- a/src/store/mainStore.js +++ b/src/store/mainStore.js @@ -80,6 +80,8 @@ export default defineStore('main', { }, }, envelopes: {}, + threads: {}, + messageToThreadDictionnary: {}, messages: {}, newMessage: undefined, showMessageComposer: false, diff --git a/src/store/mainStore/actions.js b/src/store/mainStore/actions.js index 83059563be..d92c4428e3 100644 --- a/src/store/mainStore/actions.js +++ b/src/store/mainStore/actions.js @@ -688,12 +688,10 @@ export default function mainStoreActions() { const fetchUnifiedEnvelopes = pipe( findIndividualMailboxes(this.getMailboxes, mailbox.specialRole), fetchIndividualLists, - andThen(combineEnvelopeLists(this.getPreference('sort-order'))), - andThen(sliceToPage), - andThen(tap((envelopes) => this.addEnvelopesMutation({ - envelopes, + andThen(tap((threadlist) => threadlist.forEach((threads) => this.addThreadsMutation({ + threads, query, - }))), + })))), ) return fetchUnifiedEnvelopes(this.getAccounts) @@ -701,9 +699,9 @@ export default function mainStoreActions() { return pipe( fetchEnvelopes, - andThen(tap((envelopes) => this.addEnvelopesMutation({ + andThen(tap((threads) => this.addThreadsMutation({ query, - envelopes, + threads, addToUnifiedMailboxes, }))), )(mailbox.accountId, mailboxId, query, undefined, PAGE_SIZE, this.getPreference('sort-order'), this.getPreference('layout-message-view'), includeCacheBuster ? mailbox.cacheBuster : undefined) @@ -797,7 +795,7 @@ export default function mainStoreActions() { const envelopes = nextLocalUnifiedEnvelopes(this.getAccounts) logger.debug('next unified page can be built locally and consists of ' + envelopes.length + ' envelopes', { addToUnifiedMailboxes }) - this.addEnvelopesMutation({ + this.addThreadsMutation({ query, envelopes, addToUnifiedMailboxes, @@ -820,25 +818,17 @@ export default function mainStoreActions() { return Promise.reject(new Error('Cannot find last envelope. Required for the mailbox cursor')) } - return fetchEnvelopes( - mailbox.accountId, - mailboxId, - query, - lastEnvelope.dateInt, - quantity, - this.getPreference('sort-order'), - this.getPreference('layout-message-view'), - ).then((envelopes) => { - logger.debug(`fetched ${envelopes.length} messages for mailbox ${mailboxId}`, { - envelopes, + return fetchEnvelopes(mailbox.accountId, mailboxId, query, lastEnvelope.dateInt, quantity, this.getPreference('sort-order'), this.getPreference('layout-message-view')).then((threads) => { + logger.debug(`fetched ${threads.length} messages for mailbox ${mailboxId}`, { + threads, addToUnifiedMailboxes, }) - this.addEnvelopesMutation({ + this.addThreadsMutation({ query, - envelopes, + threads, addToUnifiedMailboxes, }) - return envelopes + return threads }) }) }, @@ -891,16 +881,16 @@ export default function mainStoreActions() { const unifiedMailbox = this.getUnifiedMailbox(mailbox.specialRole) - this.addEnvelopesMutation({ - envelopes: syncData.newMessages, + this.addThreadsMutation({ + threads: syncData.newMessages, query, }) - syncData.newMessages.forEach((envelope) => { + syncData.newMessages.forEach((thread) => { if (unifiedMailbox) { - this.updateEnvelopeMutation({ + thread.forEach((envelope) => this.updateEnvelopeMutation({ envelope, - }) + })) } }) syncData.changedMessages.forEach((envelope) => { @@ -1837,7 +1827,7 @@ export default function mainStoreActions() { if (this.getPreference('layout-message-view') === 'singleton') { existing.push(envelope.databaseId) } else { - const index = existing.findIndex((id) => this.envelopes[id].threadRootId === envelope.threadRootId) + const index = existing.findIndex((id) => this.getEnvelope(id).threadRootId === envelope.threadRootId) if (index === -1) { existing.push(envelope.databaseId) } else { @@ -2018,6 +2008,37 @@ export default function mainStoreActions() { Vue.set(this.newMessage, 'type', 'outbox') Vue.set(this.newMessage.data, 'id', message.id) }, + addThreadsMutation({ + query, + threads, + addToUnifiedMailboxes = true, + }) { + if (threads.length === 0) { + return + } + const isThreaded = this.getPreference('layout-message-view') === 'threaded' + if (isThreaded) { + threads.forEach((thread) => { + const threadRootId = thread[0].threadRootId + const messages = {} + thread.forEach((message) => { + messages[message.databaseId] = message + this.messageToThreadDictionnary[message.databaseId] = threadRootId + }) + this.threads[threadRootId] = messages + }) + } else { + threads.forEach((thread) => { + this.threads[thread[0].databaseId] = { [thread[0].databaseId]: thread[0] } + }) + } + const envelopes = threads.flat() + this.addEnvelopesMutation({ + query, + envelopes, + addToUnifiedMailboxes, + }) + }, addEnvelopesMutation({ query, envelopes, @@ -2026,17 +2047,21 @@ export default function mainStoreActions() { if (envelopes.length === 0) { return } - - const idToDateInt = (id) => this.envelopes[id].dateInt + const idToDateInt = (id) => this.getEnvelope(id).dateInt const listId = normalizedEnvelopeListId(query) const orderByDateInt = orderBy(idToDateInt, this.preferences['sort-order'] === 'newest' ? 'desc' : 'asc') - envelopes.forEach((envelope) => { + if (!Object.keys(this.threads).includes(envelope.threadRootId)) { + this.threads[envelope.threadRootId] = {} + } + if (!Object.keys(this.threads[envelope.threadRootId]).includes(String(envelope.databaseId))) { + this.threads[envelope.threadRootId][envelope.databaseId] = envelope + this.messageToThreadDictionnary[envelope.databaseId] = envelope.threadRootId + } const mailbox = this.mailboxes[envelope.mailboxId] const existing = mailbox.envelopeLists[listId] || [] this.normalizeTags(envelope) - Vue.set(this.envelopes, envelope.databaseId, { ...this.envelopes[envelope.databaseId] || {}, ...envelope }) Vue.set(envelope, 'accountId', mailbox.accountId) Vue.set(mailbox.envelopeLists, listId, uniq(orderByDateInt(this.appendOrReplaceEnvelopeId(existing, envelope)))) if (!addToUnifiedMailboxes) { @@ -2057,7 +2082,7 @@ export default function mainStoreActions() { }) }, updateEnvelopeMutation({ envelope }) { - const existing = this.envelopes[envelope.databaseId] + const existing = this.getEnvelope(envelope.databaseId) if (!existing) { return } @@ -2115,8 +2140,11 @@ export default function mainStoreActions() { }) { Vue.set(envelope, 'tags', envelope.tags.filter((id) => id !== tagId)) }, + removeThreadMutation({ id }) { + Vue.delete(this.threads, id) + }, removeEnvelopeMutation({ id }) { - const envelope = this.envelopes[id] + const envelope = this.getEnvelope(id) if (!envelope) { console.warn('envelope ' + id + ' is unknown, can\'t remove it') return @@ -2139,6 +2167,16 @@ export default function mainStoreActions() { Vue.set(mailbox, 'unread', mailbox.unread - 1) } + // Remove envelope from its thread + const threadRootId = envelope.threadRootId + if (threadRootId && this.threads[threadRootId]) { + const thread = this.threads[threadRootId] + Vue.delete(thread, id) + Vue.delete(this.messageToThreadDictionnary, id) + if (Object.keys(this.threads[threadRootId]).length === 0) { + Vue.delete(this.threads, threadRootId) + } + } this.accountsUnmapped[UNIFIED_ACCOUNT_ID].mailboxes .map((mailboxId) => this.mailboxes[mailboxId]) .filter((mb) => mb.specialRole && mb.specialRole === mailbox.specialRole) @@ -2163,18 +2201,6 @@ export default function mainStoreActions() { list.splice(idx, 1) } }) - - // Delete references from other threads - for (const [key, env] of Object.entries(this.envelopes)) { - if (!env.thread) { - continue - } - - const thread = env.thread.filter((threadId) => threadId !== id) - Vue.set(this.envelopes[key], 'thread', thread) - } - - Vue.delete(this.envelopes, id) }, removeEnvelopesMutation({ id }) { Vue.set(this.mailboxes[id], 'envelopeLists', []) @@ -2220,16 +2246,15 @@ export default function mainStoreActions() { id, thread, }) { + const messages = {} // Store the envelopes, merge into any existing object if one exists thread.forEach((e) => { this.normalizeTags(e) const mailbox = this.mailboxes[e.mailboxId] Vue.set(e, 'accountId', mailbox.accountId) - Vue.set(this.envelopes, e.databaseId, { ...this.envelopes[e.databaseId] || {}, ...e }) + messages[e.databaseId] = e }) - - // Store the references - Vue.set(this.envelopes[id], 'thread', thread.map((e) => e.databaseId)) + Vue.set(this.threads, thread[0].threadRootId, messages) }, removeMessageMutation({ id }) { Vue.delete(this.messages, id) @@ -2402,29 +2427,44 @@ export default function mainStoreActions() { .filter((mailbox) => mailbox.specialRole === specialRole)) }, getEnvelope(id) { - return this.envelopes[id] + const isThreaded = this.getPreference('layout-message-view') === 'threaded' + let envelope + if (isThreaded) { + const threadRootId = this.messageToThreadDictionnary[id] + envelope = this.threads[threadRootId][id] + } else { + envelope = this.threads[id][id] + } + return envelope }, getEnvelopes(mailboxId, query) { const list = this.getMailbox(mailboxId).envelopeLists[normalizedEnvelopeListId(query)] || [] - return list.map((msgId) => this.envelopes[msgId]) + return list.map((msgId) => this.getEnvelope(msgId)).filter((message) => message) }, getEnvelopesByThreadRootId(accountId, threadRootId) { return sortBy( prop('dateInt'), - Object.values(this.envelopes).filter((envelope) => envelope.accountId === accountId && envelope.threadRootId === threadRootId), + Object.values(this.threads[threadRootId]), ) }, getMessage(id) { return this.messages[id] }, getEnvelopeThread(id) { - console.debug('get thread for envelope', id, this.envelopes[id], this.envelopes) - const thread = this.envelopes[id]?.thread ?? [] - const envelopes = thread.map((id) => this.envelopes[id]) - return sortBy(prop('dateInt'), envelopes) + const envelope = this.getEnvelope[id] + const isThreaded = this.getPreference('layout-message-view') === 'threaded' + const envelopes = isThreaded ? this.threads[envelope.threadRootId] : this.threads[id] + return sortBy(prop('dateInt'), Object.values(envelopes)) }, getEnvelopeTags(id) { - const tags = this.envelopes[id]?.tags ?? [] + const envelope = this.getEnvelope(id) + if (!envelope) { + return [] + } + if (!Array.isArray(envelope.tags)) { + this.normalizeTags(envelope) + } + const tags = envelope.tags return tags.map((tagId) => this.tags[tagId]) }, getTag(id) { diff --git a/src/util/groupedEnvelopes.js b/src/util/groupedEnvelopes.js index b8aecfeb58..8e7db8a8d2 100644 --- a/src/util/groupedEnvelopes.js +++ b/src/util/groupedEnvelopes.js @@ -4,6 +4,7 @@ */ export function groupEnvelopesByDate(envelopes, syncTimestamp, sortOrder = 'newest') { + console.log('asba',envelopes) const now = new Date(syncTimestamp) const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000)