diff --git a/lib/Contracts/IAttachmentService.php b/lib/Contracts/IAttachmentService.php index 38d6ee9a4f..243cdd186a 100644 --- a/lib/Contracts/IAttachmentService.php +++ b/lib/Contracts/IAttachmentService.php @@ -34,4 +34,5 @@ public function getAttachment(string $userId, int $id): array; * @param int $id */ public function deleteAttachment(string $userId, int $id); + } diff --git a/lib/Contracts/IMailManager.php b/lib/Contracts/IMailManager.php index 134871189d..c3f72d1f44 100644 --- a/lib/Contracts/IMailManager.php +++ b/lib/Contracts/IMailManager.php @@ -108,7 +108,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/Message.php b/lib/Db/Message.php index 0972915b85..e7e1f0a222 100644 --- a/lib/Db/Message.php +++ b/lib/Db/Message.php @@ -142,6 +142,8 @@ class Message extends Entity implements JsonSerializable { /** @var bool */ private $fetchAvatarFromClient = false; + /** @var array */ + private $attachments = []; public function __construct() { $this->from = new AddressList([]); @@ -312,6 +314,14 @@ public function getAvatar(): ?Avatar { return $this->avatar; } + public function setAttachments(array $attachments): void { + $this->attachments = $attachments; + } + + public function getAttachments(): array { + return $this->attachments; + } + #[\Override] #[ReturnTypeWillChange] public function jsonSerialize() { @@ -359,6 +369,7 @@ static function (Tag $tag) { 'mentionsMe' => $this->getMentionsMe(), 'avatar' => $this->avatar?->jsonSerialize(), 'fetchAvatarFromClient' => $this->fetchAvatarFromClient, + 'attachments' => $this->getAttachments(), ]; } } diff --git a/lib/Db/MessageMapper.php b/lib/Db/MessageMapper.php index 9068cb3771..baa486a6ca 100644 --- a/lib/Db/MessageMapper.php +++ b/lib/Db/MessageMapper.php @@ -756,10 +756,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') @@ -768,7 +769,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()); } @@ -1273,10 +1274,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 []; } @@ -1288,7 +1290,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) { @@ -1298,6 +1300,50 @@ 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 []; + } + + $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('id', $qb->createParameter('ids')) + ) + ->orderBy('sent_at', $sortOrder); + $results = []; + foreach (array_chunk($ids, 1000) as $chunk) { + $qb->setParameter('ids', $chunk, IQueryBuilder::PARAM_INT_ARRAY); + if ($threadingEnabled) { + $res = $qb->executeQuery(); + while ($row = $res->fetch()) { + $message = $this->mapRowToEntity($row); + if ($message->getThreadRootId() === null) { + $results[] = [$message]; + } else { + $results[] = $this->findThread($account, $message->getThreadRootId(), $sortOrder); + } + } + $res->closeCursor(); + } else { + $results[] = array_map(fn (Message $msg) => [$msg], $this->findRelatedData($this->findEntities($qb), $userId)); + } + } + return $threadingEnabled ? $results : array_merge([], ...$results); + } + /** * @param string $userId * @param int[] $ids @@ -1325,6 +1371,48 @@ 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 []; + } + $qb = $this->db->getQueryBuilder(); + $qb->select('*') + ->from($this->getTableName()) + ->where( + $qb->expr()->in('id', $qb->createParameter('ids')) + ) + ->orderBy('sent_at', $sortOrder); + + $results = []; + foreach (array_chunk($ids, 1000) as $chunk) { + $qb->setParameter('ids', $chunk, IQueryBuilder::PARAM_INT_ARRAY); + if ($threadingEnabled) { + $res = $qb->executeQuery(); + while ($row = $res->fetch()) { + $message = $this->mapRowToEntity($row); + if ($message->getThreadRootId() === null) { + $results[] = [$message]; + } else { + $results[] = $this->findThread($account, $message->getThreadRootId(), $sortOrder); + } + } + $res->closeCursor(); + } else { + $results[] = array_map(fn (Message $msg) => [$msg], $this->findRelatedData($this->findEntities($qb), $userId)); + } + } + return $threadingEnabled ? $results : array_merge([], ...$results); + } + /** * @param Message[] $messages * diff --git a/lib/IMAP/PreviewEnhancer.php b/lib/IMAP/PreviewEnhancer.php index eadc094cac..0021be7cdd 100644 --- a/lib/IMAP/PreviewEnhancer.php +++ b/lib/IMAP/PreviewEnhancer.php @@ -15,6 +15,7 @@ use OCA\Mail\Db\Message; use OCA\Mail\Db\MessageMapper as DbMapper; use OCA\Mail\IMAP\MessageMapper as ImapMapper; +use OCA\Mail\Service\Attachment\AttachmentService; use OCA\Mail\Service\Avatar\Avatar; use OCA\Mail\Service\AvatarService; use Psr\Log\LoggerInterface; @@ -39,11 +40,14 @@ class PreviewEnhancer { /** @var AvatarService */ private $avatarService; - public function __construct(IMAPClientFactory $clientFactory, + public function __construct( + IMAPClientFactory $clientFactory, ImapMapper $imapMapper, DbMapper $dbMapper, LoggerInterface $logger, - AvatarService $avatarService) { + AvatarService $avatarService, + private AttachmentService $attachmentService, + ) { $this->clientFactory = $clientFactory; $this->imapMapper = $imapMapper; $this->mapper = $dbMapper; @@ -52,9 +56,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) { @@ -65,6 +69,12 @@ public function process(Account $account, Mailbox $mailbox, array $messages, boo return array_merge($carry, [$message->getUid()]); }, []); + $client = $this->clientFactory->getClient($account); + + foreach ($messages as $message) { + $attachments = $this->attachmentService->getAttachmentNames($account, $mailbox, $message, $client); + $message->setAttachments($attachments); + } if ($preLoadAvatars) { foreach ($messages as $message) { @@ -87,7 +97,7 @@ public function process(Account $account, Mailbox $mailbox, array $messages, boo return $messages; } - $client = $this->clientFactory->getClient($account); + try { $data = $this->imapMapper->getBodyStructureData( $client, diff --git a/lib/Model/IMAPMessage.php b/lib/Model/IMAPMessage.php index 3a973492f7..003c0add40 100644 --- a/lib/Model/IMAPMessage.php +++ b/lib/Model/IMAPMessage.php @@ -283,6 +283,7 @@ public function getSentDate(): Horde_Imap_Client_DateTime { return $this->imapDate; } + /** * @param int $id * @@ -384,7 +385,7 @@ public function setContent(string $content) { */ #[\Override] public function getAttachments(): array { - throw new Exception('not implemented'); + return $this->attachments; } /** diff --git a/lib/Service/Attachment/AttachmentService.php b/lib/Service/Attachment/AttachmentService.php index 97655a09af..dd3abe9ca5 100644 --- a/lib/Service/Attachment/AttachmentService.php +++ b/lib/Service/Attachment/AttachmentService.php @@ -18,7 +18,10 @@ use OCA\Mail\Db\LocalAttachment; use OCA\Mail\Db\LocalAttachmentMapper; use OCA\Mail\Db\LocalMessage; +use OCA\Mail\Db\Mailbox; +use OCA\Mail\Db\Message; use OCA\Mail\Exception\AttachmentNotFoundException; +use OCA\Mail\Exception\ServiceException; use OCA\Mail\Exception\UploadException; use OCA\Mail\IMAP\MessageMapper; use OCP\AppFramework\Db\DoesNotExistException; @@ -26,6 +29,7 @@ use OCP\Files\Folder; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; +use OCP\ICacheFactory; use Psr\Log\LoggerInterface; class AttachmentService implements IAttachmentService { @@ -51,6 +55,10 @@ class AttachmentService implements IAttachmentService { * @var LoggerInterface */ private $logger; + /** + * @var ICache + */ + private $cache; /** * @param Folder $userFolder @@ -60,6 +68,7 @@ public function __construct($userFolder, AttachmentStorage $storage, IMailManager $mailManager, MessageMapper $imapMessageMapper, + ICacheFactory $cacheFactory, LoggerInterface $logger) { $this->mapper = $mapper; $this->storage = $storage; @@ -67,6 +76,7 @@ public function __construct($userFolder, $this->messageMapper = $imapMessageMapper; $this->userFolder = $userFolder; $this->logger = $logger; + $this->cache = $cacheFactory->createLocal('mail.attachment_names'); } /** @@ -249,6 +259,32 @@ public function handleAttachments(Account $account, array $attachments, \Horde_I return array_values(array_filter($attachmentIds)); } + public function getAttachmentNames(Account $account, Mailbox $mailbox, Message $message, \Horde_Imap_Client_Socket $client): array { + $attachments = []; + $uniqueCacheId = $account->getUserId() . $account->getId() . $mailbox->getId() . $message->getUid(); + $cached = $this->cache->get($uniqueCacheId); + if ($cached) { + return $cached; + } + try { + $imapMessage = $this->mailManager->getImapMessage( + $client, + $account, + $mailbox, + $message->getUid(), + true + ); + $attachments = $imapMessage->getAttachments(); + } catch (ServiceException $e) { + $this->logger->error('Could not get attachment names', ['exception' => $e, 'messageId' => $message->getUid()]); + } + $result = array_map(static function (array $attachment) { + return ['name' => $attachment['fileName'],'mime' => $attachment['mime']]; + }, $attachments); + $this->cache->set($uniqueCacheId, $result); + return $result; + } + /** * Add a message as attachment * diff --git a/lib/Service/MailManager.php b/lib/Service/MailManager.php index 2491fe2239..d956910cba 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; @@ -236,8 +237,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 2d1d1181d9..454b3ebf8f 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,7 +195,7 @@ 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 function (Message $msg) { return $msg->getId(); @@ -186,7 +203,7 @@ private function getDatabaseSyncChanges(Account $account, $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/AttachmentTag.vue b/src/components/AttachmentTag.vue new file mode 100644 index 0000000000..cf8c7aad1f --- /dev/null +++ b/src/components/AttachmentTag.vue @@ -0,0 +1,43 @@ + + + + + diff --git a/src/components/Envelope.vue b/src/components/Envelope.vue index 53518fc416..8d54b6dddd 100644 --- a/src/components/Envelope.vue +++ b/src/components/Envelope.vue @@ -332,6 +332,9 @@ {{ translateTagDisplayName(tag) }} +
+ +
(envelope?.attachments)).flat() + }, draggableLabel() { let label = this.data.subject const sender = this.data.from[0]?.label ?? this.data.from[0]?.email diff --git a/src/components/EnvelopeList.vue b/src/components/EnvelopeList.vue index 1ce92e4749..7812b8b8c1 100644 --- a/src/components/EnvelopeList.vue +++ b/src/components/EnvelopeList.vue @@ -113,14 +113,14 @@
{ - return a.dateInt < b.dateInt ? -1 : 1 + return Object.values(a)[0].dateInt < Object.values(b)[0].dateInt ? -1 : 1 }) } return [...this.envelopes] @@ -252,16 +252,16 @@ export default { return this.selection.length > 0 }, isAtLeastOneSelectedRead() { - return this.selectedEnvelopes.some((env) => env.flags.seen === true) + return this.selectedEnvelopes.some((env) => Object.values(env)[0].flags.seen === true) }, isAtLeastOneSelectedUnread() { - return this.selectedEnvelopes.some((env) => env.flags.seen === false) + return this.selectedEnvelopes.some((env) => Object.values(env)[0].flags.seen === false) }, isAtLeastOneSelectedImportant() { // returns true if at least one selected message is marked as important return this.selectedEnvelopes.some((env) => { return this.mainStore - .getEnvelopeTags(env.databaseId) + .getEnvelopeTags(Object.keys(env)[0].databaseId) .some((tag) => tag.imapLabel === '$label1') }) }, @@ -269,33 +269,33 @@ export default { // returns true if at least one selected message is not marked as important return this.selectedEnvelopes.some((env) => { return !this.mainStore - .getEnvelopeTags(env.databaseId) + .getEnvelopeTags(Object.keys(env)[0].databaseId) .some((tag) => tag.imapLabel === '$label1') }) }, isAtLeastOneSelectedJunk() { // returns true if at least one selected message is marked as junk return this.selectedEnvelopes.some((env) => { - return env.flags.$junk + return Object.values(env)[0].flags.$junk }) }, isAtLeastOneSelectedNotJunk() { // returns true if at least one selected message is not marked as not junk return this.selectedEnvelopes.some((env) => { - return !env.flags.$junk + return !Object.values(env)[0].flags.$junk }) }, isAtLeastOneSelectedFavorite() { - return this.selectedEnvelopes.some((env) => env.flags.flagged) + return this.selectedEnvelopes.some((env) => Object.values(env)[0].flags.flagged) }, isAtLeastOneSelectedUnFavorite() { - return this.selectedEnvelopes.some((env) => !env.flags.flagged) + return this.selectedEnvelopes.some((env) => !Object.values(env)[0].flags.flagged) }, selectedEnvelopes() { - return this.sortedEnvelops.filter((env) => this.selection.includes(env.databaseId)) + return this.sortedEnvelops.filter((env) => this.selection.includes(Object.keys(env)[0].databaseId)) }, hasMultipleAccounts() { - const mailboxIds = this.sortedEnvelops.map(envelope => envelope.mailboxId) + const mailboxIds = this.sortedEnvelops.map(envelope => Object.values(envelope)[0].mailboxId) return Array.from(new Set(mailboxIds)).length > 1 }, listTransitionName() { @@ -304,10 +304,12 @@ export default { }, watch: { sortedEnvelops(newVal, oldVal) { + const newEnvs = Object.values(newVal).map(thread => Object.values(thread)[0]) + const oldEnvs = Object.values(oldVal).map(thread => Object.values(thread)[0]) // Unselect vanished envelopes - const newIds = newVal.map((env) => env.databaseId) + const newIds = newEnvs.map((env) => env.databaseId) this.selection = this.selection.filter((id) => newIds.includes(id)) - differenceWith((a, b) => a.databaseId === b.databaseId, oldVal, newVal) + differenceWith((a, b) => a.databaseId === b.databaseId, oldEnvs, newEnvs) .forEach((env) => { env.flags.selected = false }) @@ -417,8 +419,8 @@ export default { // one of threads is selected if (indexSelectedEnvelope !== -1) { - const lastSelectedEnvelope = this.selectedEnvelopes[this.selectedEnvelopes.length - 1] - const diff = this.sortedEnvelops.filter(envelope => envelope === lastSelectedEnvelope || !this.selectedEnvelopes.includes(envelope)) + const lastSelectedEnvelope = this.selectedEnvelopes[this.selectedEnvelopes.length - 1][0] + const diff = this.sortedEnvelops.filter(envelope => envelope[0] === lastSelectedEnvelope || !this.selectedEnvelopes.includes(envelope)) const lastIndex = diff.indexOf(lastSelectedEnvelope) nextEnvelopeToNavigate = diff[lastIndex === 0 ? 1 : lastIndex - 1] } @@ -466,13 +468,13 @@ export default { this.unselectAll() }, setEnvelopeSelected(envelope, selected) { - const alreadySelected = this.selection.includes(envelope.databaseId) + const alreadySelected = this.selection.includes(envelope[0].databaseId) if (selected && !alreadySelected) { - envelope.flags.selected = true - this.selection.push(envelope.databaseId) + envelope[0].flags.selected = true + this.selection.push(envelope[0].databaseId) } else if (!selected && alreadySelected) { - envelope.flags.selected = false - this.selection.splice(this.selection.indexOf(envelope.databaseId), 1) + envelope[0].flags.selected = false + this.selection.splice(this.selection.indexOf(envelope[0].databaseId), 1) } }, onEnvelopeSelectToggle(envelope, index, selected) { @@ -528,7 +530,7 @@ export default { */ findSelectionIndex(databaseId) { for (const [index, envelope] of this.sortedEnvelops.entries()) { - if (envelope.databaseId === databaseId) { + if (envelope[0].databaseId === databaseId) { return index } } diff --git a/src/components/MailboxThread.vue b/src/components/MailboxThread.vue index e07337a9ef..50db8f126e 100644 --- a/src/components/MailboxThread.vue +++ b/src/components/MailboxThread.vue @@ -356,7 +356,7 @@ export default { } for (const envelope of envelopes) { - const date = new Date(envelope.dateInt * 1000) + const date = new Date(Object.values(envelope)[0].dateInt * 1000) if (date >= oneHourAgo) { groups.lastHour.push(envelope) } else if (date >= startOfToday) { diff --git a/src/components/icons/FileIcon.vue b/src/components/icons/FileIcon.vue new file mode 100644 index 0000000000..2ec4c0b8f1 --- /dev/null +++ b/src/components/icons/FileIcon.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/src/service/MessageService.js b/src/service/MessageService.js index 69ab0a5148..c284aae8ab 100644 --- a/src/service/MessageService.js +++ b/src/service/MessageService.js @@ -62,7 +62,7 @@ export function fetchEnvelopes(accountId, mailboxId, query, cursor, limit, sort, params, }) .then((resp) => resp.data) - .then(envelopes => envelopes.map(amendEnvelopeWithIds(accountId))) + .then(envelopes => envelopes.map((messageList) => messageList.map(amendEnvelopeWithIds(accountId)))) .catch((error) => { throw convertAxiosError(error) }) @@ -95,7 +95,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((messageList) => messageList.map(amend)), changedMessages: response.data.changedMessages.map(amend), vanishedMessages: response.data.vanishedMessages, stats: response.data.stats, diff --git a/src/store/constants.js b/src/store/constants.js index d34e342e58..ee76ba8f4e 100644 --- a/src/store/constants.js +++ b/src/store/constants.js @@ -18,3 +18,6 @@ export const STATUS_IMAP_SENT_MAILBOX_FAIL = 11 export const STATUS_SMTP_ERROR = 13 export const FOLLOW_UP_TAG_LABEL = '$follow_up' +export const FILE_EXTENSIONS_WORD_PROCESSING = ['doc', 'docx', 'dot', 'odt', 'dotx', 'odt', 'ott'] +export const FILE_EXTENSIONS_SPREADSHEET = ['xls', 'xlsx', 'ods'] +export const FILE_EXTENSIONS_PRESENTATION = ['ppt', 'pptx', 'odp', 'otp', 'pps', 'ppsx', 'pot', 'potx'] diff --git a/src/store/mainStore/actions.js b/src/store/mainStore/actions.js index 0f790a1692..c529ceefcf 100644 --- a/src/store/mainStore/actions.js +++ b/src/store/mainStore/actions.js @@ -654,7 +654,7 @@ export default function mainStoreActions() { // Only commit if not undefined (not found) if (envelope) { this.addEnvelopesMutation({ - envelopes: [envelope], + envelopes: [[envelope]], }) } @@ -1853,16 +1853,7 @@ export default function mainStoreActions() { */ appendOrReplaceEnvelopeId(existing, envelope) { - if (this.getPreference('layout-message-view') === 'singleton') { - existing.push(envelope.databaseId) - } else { - const index = existing.findIndex((id) => this.envelopes[id].threadRootId === envelope.threadRootId) - if (index === -1) { - existing.push(envelope.databaseId) - } else { - existing[index] = envelope.databaseId - } - } + existing.push(envelope.databaseId) return existing }, @@ -2051,13 +2042,20 @@ export default function mainStoreActions() { const listId = normalizedEnvelopeListId(query) const orderByDateInt = orderBy(idToDateInt, this.preferences['sort-order'] === 'newest' ? 'desc' : 'asc') - envelopes.forEach((envelope) => { - const mailbox = this.mailboxes[envelope.mailboxId] + envelopes.forEach((envelopelist) => { + const mailbox = this.mailboxes[envelopelist[0].mailboxId] const existing = mailbox.envelopeLists[listId] || [] - this.normalizeTags(envelope) - Vue.set(this.envelopes, envelope.databaseId, Object.assign({}, this.envelopes[envelope.databaseId] || {}, envelope)) - Vue.set(envelope, 'accountId', mailbox.accountId) - Vue.set(mailbox.envelopeLists, listId, uniq(orderByDateInt(this.appendOrReplaceEnvelopeId(existing, envelope)))) + envelopelist.forEach((envelope) => { + this.normalizeTags(envelope) + }) + const envelopeListIndexed = [] + envelopelist.forEach((envelope) => { + envelopeListIndexed[envelope.databaseId] = envelope + }) + Vue.set(this.envelopes, envelopelist[0].databaseId, Object.assign({}, this.envelopes[envelopelist[0].databaseId] || {}, envelopeListIndexed)) + Vue.set(envelopelist[0], 'accountId', mailbox.accountId) + // TODO: Check if still neededed + Vue.set(mailbox.envelopeLists, listId, uniq(orderByDateInt(this.appendOrReplaceEnvelopeId(existing, envelopelist[0])))) if (!addToUnifiedMailboxes) { return } @@ -2070,7 +2068,7 @@ export default function mainStoreActions() { Vue.set( mailbox.envelopeLists, listId, - uniq(orderByDateInt(existing.concat([envelope.databaseId]))), + uniq(orderByDateInt(existing.concat([envelopelist[0].databaseId]))), ) }) }) @@ -2135,7 +2133,9 @@ export default function mainStoreActions() { Vue.set(envelope, 'tags', envelope.tags.filter((id) => id !== tagId)) }, removeEnvelopeMutation({ id }) { - const envelope = this.envelopes[id] + const envelope = Object.values(this.envelopes) + .flatMap(envList => Object.values(envList)) + .find(env => env.databaseId === id) if (!envelope) { console.warn('envelope ' + id + ' is unknown, can\'t remove it') return