diff --git a/appinfo/info.xml b/appinfo/info.xml
index d964279f3d..c7037f13f4 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -12,7 +12,7 @@
- **🙈 We’re not reinventing the wheel!** Based on the great [Horde](http://horde.org) libraries.
- **📬 Want to host your own mail server?** We don’t have to reimplement this as you could set up [Mail-in-a-Box](https://mailinabox.email)!
]]>
- 1.3.3
+ 1.4.0
agpl
Christoph Wurst
Roeland Jago Douma
diff --git a/css/mail.scss b/css/mail.scss
index e66fac7b5a..bdd23ac5ca 100755
--- a/css/mail.scss
+++ b/css/mail.scss
@@ -331,6 +331,9 @@
.icon-drafts {
@include icon-color('drafts', 'mail', $color-black);
}
+.icon-important {
+ @include icon-color('important', 'mail', $color-black);
+}
.icon-sent {
@include icon-color('sent', 'mail', $color-black);
}
diff --git a/img/important.svg b/img/important.svg
new file mode 100644
index 0000000000..710dfffbd5
--- /dev/null
+++ b/img/important.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lib/Db/Message.php b/lib/Db/Message.php
index e2c0bbb4bb..c909b73480 100644
--- a/lib/Db/Message.php
+++ b/lib/Db/Message.php
@@ -61,6 +61,8 @@
* @method bool getStructureAnalyzed()
* @method void setFlagAttachments(?bool $hasAttachments)
* @method null|bool getFlagAttachments()
+ * @method void setFlagImportant(bool $important)
+ * @method bool getFlagImportant()
* @method void setPreviewText(?string $subject)
* @method null|string getPreviewText()
* @method void setUpdatedAt(int $time)
@@ -76,6 +78,7 @@ class Message extends Entity implements JsonSerializable {
'forwarded',
'junk',
'notjunk',
+ 'important',
];
protected $uid;
@@ -94,6 +97,7 @@ class Message extends Entity implements JsonSerializable {
protected $updatedAt;
protected $structureAnalyzed;
protected $flagAttachments;
+ protected $flagImportant;
protected $previewText;
/** @var AddressList */
@@ -126,6 +130,7 @@ public function __construct() {
$this->addType('flagNotjunk', 'bool');
$this->addType('structureAnalyzed', 'bool');
$this->addType('flagAttachments', 'bool');
+ $this->addType('flagImportant', 'bool');
$this->addType('updatedAt', 'integer');
}
@@ -210,6 +215,7 @@ public function jsonSerialize() {
'draft' => $this->getFlagDraft(),
'forwarded' => $this->getFlagForwarded(),
'hasAttachments' => $this->getFlagAttachments() ?? false,
+ 'important' => $this->getFlagImportant(),
],
'from' => $this->getFrom()->jsonSerialize(),
'to' => $this->getTo()->jsonSerialize(),
diff --git a/lib/Migration/Version1040Date20200422130220.php b/lib/Migration/Version1040Date20200422130220.php
new file mode 100644
index 0000000000..d830cc4ce7
--- /dev/null
+++ b/lib/Migration/Version1040Date20200422130220.php
@@ -0,0 +1,56 @@
+connection = $connection;
+ }
+
+ /**
+ * @param IOutput $output
+ * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
+ * @param array $options
+ *
+ * @return ISchemaWrapper
+ */
+ public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ISchemaWrapper {
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+
+ $schema->dropTable('mail_messages');
+
+ return $schema;
+ }
+
+ public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options) {
+ // Reset locks and sync tokens
+ $qb1 = $this->connection->getQueryBuilder();
+ $updateMailboxes = $qb1->update('mail_mailboxes')
+ ->set('sync_new_lock', $qb1->createNamedParameter(null))
+ ->set('sync_new_token', $qb1->createNamedParameter(null))
+ ->set('sync_changed_lock', $qb1->createNamedParameter(null))
+ ->set('sync_changed_token', $qb1->createNamedParameter(null))
+ ->set('sync_vanished_lock', $qb1->createNamedParameter(null))
+ ->set('sync_vanished_token', $qb1->createNamedParameter(null));
+ $updateMailboxes->execute();
+
+ // Clean up some orphaned data
+ $qb2 = $this->connection->getQueryBuilder();
+ $deleteRecipients = $qb2->delete('mail_recipients');
+ $deleteRecipients->execute();
+ }
+
+}
diff --git a/lib/Migration/Version1040Date20200422142920.php b/lib/Migration/Version1040Date20200422142920.php
new file mode 100644
index 0000000000..666a1b92c3
--- /dev/null
+++ b/lib/Migration/Version1040Date20200422142920.php
@@ -0,0 +1,117 @@
+createTable('mail_messages');
+ $messagesTable->addColumn('id', 'integer', [
+ 'autoincrement' => true,
+ 'notnull' => true,
+ 'length' => 20,
+ ]);
+ $messagesTable->addColumn('uid', 'integer', [
+ 'notnull' => true,
+ 'length' => 4,
+ ]);
+ $messagesTable->addColumn('message_id', 'string', [
+ 'notnull' => false,
+ 'length' => 255,
+ ]);
+ $messagesTable->addColumn('mailbox_id', 'integer', [
+ 'notnull' => true,
+ 'length' => 20,
+ ]);
+ $messagesTable->addColumn('subject', 'string', [
+ 'notnull' => true,
+ 'length' => 255,
+ 'default' => '',
+ ]);
+ $messagesTable->addColumn('sent_at', 'integer', [
+ 'notnull' => true,
+ 'length' => 4,
+ ]);
+ $messagesTable->addColumn('flag_answered', 'boolean', [
+ 'notnull' => true,
+ 'default' => false,
+ ]);
+ $messagesTable->addColumn('flag_deleted', 'boolean', [
+ 'notnull' => true,
+ 'default' => false,
+ ]);
+ $messagesTable->addColumn('flag_draft', 'boolean', [
+ 'notnull' => true,
+ 'default' => false,
+ ]);
+ $messagesTable->addColumn('flag_flagged', 'boolean', [
+ 'notnull' => true,
+ 'default' => false,
+ ]);
+ $messagesTable->addColumn('flag_seen', 'boolean', [
+ 'notnull' => true,
+ 'default' => false,
+ ]);
+ $messagesTable->addColumn('flag_forwarded', 'boolean', [
+ 'notnull' => true,
+ 'default' => false,
+ ]);
+ $messagesTable->addColumn('flag_junk', 'boolean', [
+ 'notnull' => true,
+ 'default' => false,
+ ]);
+ $messagesTable->addColumn('flag_notjunk', 'boolean', [
+ 'notnull' => true,
+ 'default' => false,
+ ]);
+ $messagesTable->addColumn('flag_attachments', 'boolean', [
+ 'notnull' => false,
+ ]);
+ $messagesTable->addColumn('flag_important', 'boolean', [
+ 'notnull' => true,
+ 'default' => false,
+ ]);
+ $messagesTable->addColumn('structure_analyzed', 'boolean', [
+ 'notnull' => true,
+ 'default' => false,
+ ]);
+ $messagesTable->addColumn('preview_text', 'string', [
+ 'notnull' => false,
+ 'length' => 255,
+ ]);
+ $messagesTable->addColumn('updated_at', 'integer', [
+ 'notnull' => false,
+ 'length' => 4,
+ ]);
+ $messagesTable->setPrimaryKey(['id']);
+ // We allow each UID just once
+ $messagesTable->addUniqueIndex(
+ [
+ 'uid',
+ 'mailbox_id',
+ ],
+ 'mail_msg_mb_uid_idx'
+ );
+ $messagesTable->addIndex(['sent_at'], 'mail_msg_sent_idx');
+
+ return $schema;
+ }
+
+}
diff --git a/lib/Model/IMAPMessage.php b/lib/Model/IMAPMessage.php
index 3b7be10606..3e055f2ccc 100644
--- a/lib/Model/IMAPMessage.php
+++ b/lib/Model/IMAPMessage.php
@@ -646,6 +646,7 @@ public function toDbMessage(int $mailboxId): \OCA\Mail\Db\Message {
$msg->setFlagForwarded(in_array(Horde_Imap_Client::FLAG_FORWARDED, $flags, true));
$msg->setFlagJunk(in_array(Horde_Imap_Client::FLAG_JUNK, $flags, true));
$msg->setFlagNotjunk(in_array(Horde_Imap_Client::FLAG_NOTJUNK, $flags, true));
+ $msg->setFlagImportant(false);
$msg->setFlagAttachments(false);
return $msg;
diff --git a/lib/Service/Classification/OftenContactedSenderClassifier.php b/lib/Service/Classification/OftenContactedSenderClassifier.php
new file mode 100644
index 0000000000..4fe95ff0ef
--- /dev/null
+++ b/lib/Service/Classification/OftenContactedSenderClassifier.php
@@ -0,0 +1,102 @@
+
+ *
+ * @author 2020 Christoph Wurst
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace OCA\Mail\Service\Classification;
+
+use OCA\Mail\Account;
+use OCA\Mail\Address;
+use OCA\Mail\Db\Mailbox;
+use OCA\Mail\Db\MailboxMapper;
+use OCA\Mail\Db\Message;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+
+class OftenContactedSenderClassifier extends AClassifier {
+ use SafeRatio;
+
+ /** @var MailboxMapper */
+ private $mailboxMapper;
+
+ /** @var IDBConnection */
+ private $db;
+
+ public function __construct(MailboxMapper $mailboxMapper,
+ IDBConnection $db) {
+ $this->mailboxMapper = $mailboxMapper;
+ $this->db = $db;
+ }
+
+ public function isImportant(Account $account, Mailbox $mailbox, Message $message): bool {
+ $sender = $message->getTo()->first();
+ if ($sender === null) {
+ return false;
+ }
+
+ try {
+ $mb = $this->mailboxMapper->findSpecial($account, 'sent');
+ } catch (DoesNotExistException $e) {
+ return false;
+ }
+
+ return $this->greater(
+ $this->getMessagesSentTo($mb, $sender->getEmail()),
+ $this->getMessagesSentTotal($mb),
+ 0.1,
+ true // The very first message is important
+ );
+ }
+
+ private function getMessagesSentTotal(Mailbox $mb): int {
+ $qb = $this->db->getQueryBuilder();
+
+ $select = $qb->select($qb->func()->count('*'))
+ ->from('mail_recipients', 'r')
+ ->join('r', 'mail_messages', 'm', $qb->expr()->eq('m.id', 'r.message_id', IQueryBuilder::PARAM_INT))
+ ->join('r', 'mail_mailboxes', 'mb', $qb->expr()->eq('mb.id', $qb->expr()->castColumn('m.mailbox_id', IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT))
+ ->where($qb->expr()->eq('r.id', $qb->createNamedParameter(Address::TYPE_FROM), IQueryBuilder::PARAM_INT))
+ ->andWhere($qb->expr()->eq('mb.id', $qb->createNamedParameter($mb->getId(), IQueryBuilder::PARAM_INT)));
+ $result = $select->execute();
+ $cnt = $result->fetchColumn();
+ $result->closeCursor();
+ return (int)$cnt;
+ }
+
+ private function getMessagesSentTo(Mailbox $mb, string $email): int {
+ $qb = $this->db->getQueryBuilder();
+
+ $select = $qb->select($qb->func()->count('*'))
+ ->from('mail_recipients', 'r')
+ ->join('r', 'mail_messages', 'm', $qb->expr()->eq('m.id', 'r.message_id', IQueryBuilder::PARAM_INT))
+ ->join('r', 'mail_mailboxes', 'mb', $qb->expr()->eq('mb.id', $qb->expr()->castColumn('m.mailbox_id', IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT))
+ ->where($qb->expr()->eq('r.id', $qb->createNamedParameter(Address::TYPE_FROM), IQueryBuilder::PARAM_INT))
+ ->andWhere($qb->expr()->eq('r.email', $qb->createNamedParameter($email)))
+ ->andWhere($qb->expr()->eq('mb.id', $qb->createNamedParameter($mb->getId(), IQueryBuilder::PARAM_INT)));
+ $result = $select->execute();
+ $cnt = $result->fetchColumn();
+ $result->closeCursor();
+ return (int)$cnt;
+ }
+}
diff --git a/lib/Service/Classification/OftenImportantSenderClassifier.php b/lib/Service/Classification/OftenImportantSenderClassifier.php
new file mode 100644
index 0000000000..fb95cda04b
--- /dev/null
+++ b/lib/Service/Classification/OftenImportantSenderClassifier.php
@@ -0,0 +1,103 @@
+
+ *
+ * @author 2020 Christoph Wurst
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace OCA\Mail\Service\Classification;
+
+use OCA\Mail\Account;
+use OCA\Mail\Address;
+use OCA\Mail\Db\Mailbox;
+use OCA\Mail\Db\MailboxMapper;
+use OCA\Mail\Db\Message;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+
+class OftenImportantSenderClassifier extends AClassifier {
+ use SafeRatio;
+
+ /** @var MailboxMapper */
+ private $mailboxMapper;
+
+ /** @var IDBConnection */
+ private $db;
+
+ public function __construct(MailboxMapper $mailboxMapper,
+ IDBConnection $db) {
+ $this->mailboxMapper = $mailboxMapper;
+ $this->db = $db;
+ }
+
+ public function isImportant(Account $account, Mailbox $mailbox, Message $message): bool {
+ $sender = $message->getTo()->first();
+ if ($sender === null) {
+ return false;
+ }
+
+ try {
+ $mb = $this->mailboxMapper->findSpecial($account, 'inbox');
+ } catch (DoesNotExistException $e) {
+ return false;
+ }
+
+ return $this->greater(
+ $this->getNrOfImportantMessages($mb, $sender->getEmail()),
+ $this->getNumberOfMessages($mb, $sender->getEmail()),
+ 0.3
+ );
+ }
+
+ private function getNrOfImportantMessages(Mailbox $mb, string $email): int {
+ $qb = $this->db->getQueryBuilder();
+
+ $select = $qb->select($qb->func()->count('*'))
+ ->from('mail_recipients', 'r')
+ ->join('r', 'mail_messages', 'm', $qb->expr()->eq('m.id', 'r.message_id', IQueryBuilder::PARAM_INT))
+ ->join('r', 'mail_mailboxes', 'mb', $qb->expr()->eq('mb.id', $qb->expr()->castColumn('m.mailbox_id', IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT))
+ ->where($qb->expr()->eq('r.type', $qb->createNamedParameter(Address::TYPE_FROM), IQueryBuilder::PARAM_INT))
+ ->andWhere($qb->expr()->eq('mb.id', $qb->createNamedParameter($mb->getId(), IQueryBuilder::PARAM_INT)))
+ ->andWhere($qb->expr()->eq('m.flag_important', $qb->createNamedParameter(true, IQueryBuilder::PARAM_BOOL)))
+ ->andWhere($qb->expr()->eq('r.email', $qb->createNamedParameter($email)));
+ $result = $select->execute();
+ $cnt = $result->fetchColumn();
+ $result->closeCursor();
+ return (int)$cnt;
+ }
+
+ private function getNumberOfMessages(Mailbox $mb, string $email): int {
+ $qb = $this->db->getQueryBuilder();
+
+ $select = $qb->select($qb->func()->count('*'))
+ ->from('mail_recipients', 'r')
+ ->join('r', 'mail_messages', 'm', $qb->expr()->eq('m.id', 'r.message_id', IQueryBuilder::PARAM_INT))
+ ->join('r', 'mail_mailboxes', 'mb', $qb->expr()->eq('mb.id', $qb->expr()->castColumn('m.mailbox_id', IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT))
+ ->where($qb->expr()->eq('r.type', $qb->createNamedParameter(Address::TYPE_FROM), IQueryBuilder::PARAM_INT))
+ ->andWhere($qb->expr()->eq('mb.id', $qb->createNamedParameter($mb->getId(), IQueryBuilder::PARAM_INT)))
+ ->andWhere($qb->expr()->eq('r.email', $qb->createNamedParameter($email)));
+ $result = $select->execute();
+ $cnt = $result->fetchColumn();
+ $result->closeCursor();
+ return (int)$cnt;
+ }
+}
diff --git a/lib/Service/Classification/OftenReadSenderClassifier.php b/lib/Service/Classification/OftenReadSenderClassifier.php
new file mode 100644
index 0000000000..7eaf86e576
--- /dev/null
+++ b/lib/Service/Classification/OftenReadSenderClassifier.php
@@ -0,0 +1,103 @@
+
+ *
+ * @author 2020 Christoph Wurst
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace OCA\Mail\Service\Classification;
+
+use OCA\Mail\Account;
+use OCA\Mail\Address;
+use OCA\Mail\Db\Mailbox;
+use OCA\Mail\Db\MailboxMapper;
+use OCA\Mail\Db\Message;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+
+class OftenReadSenderClassifier extends AClassifier {
+ use SafeRatio;
+
+ /** @var MailboxMapper */
+ private $mailboxMapper;
+
+ /** @var IDBConnection */
+ private $db;
+
+ public function __construct(MailboxMapper $mailboxMapper,
+ IDBConnection $db) {
+ $this->mailboxMapper = $mailboxMapper;
+ $this->db = $db;
+ }
+
+ public function isImportant(Account $account, Mailbox $mailbox, Message $message): bool {
+ $sender = $message->getTo()->first();
+ if ($sender === null) {
+ return false;
+ }
+
+ try {
+ $mb = $this->mailboxMapper->findSpecial($account, 'inbox');
+ } catch (DoesNotExistException $e) {
+ return false;
+ }
+
+ return $this->greater(
+ $this->getNrOfReadMessages($mb, $sender->getEmail()),
+ $this->getNumberOfMessages($mb, $sender->getEmail()),
+ 0.9
+ );
+ }
+
+ private function getNrOfReadMessages(Mailbox $mb, string $email): int {
+ $qb = $this->db->getQueryBuilder();
+
+ $select = $qb->select($qb->func()->count('*'))
+ ->from('mail_recipients', 'r')
+ ->join('r', 'mail_messages', 'm', $qb->expr()->eq('m.id', 'r.message_id', IQueryBuilder::PARAM_INT))
+ ->join('r', 'mail_mailboxes', 'mb', $qb->expr()->eq('mb.id', $qb->expr()->castColumn('m.mailbox_id', IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT))
+ ->where($qb->expr()->eq('r.type', $qb->createNamedParameter(Address::TYPE_FROM), IQueryBuilder::PARAM_INT))
+ ->andWhere($qb->expr()->eq('mb.id', $qb->createNamedParameter($mb->getId(), IQueryBuilder::PARAM_INT)))
+ ->andWhere($qb->expr()->eq('m.flag_seen', $qb->createNamedParameter(true, IQueryBuilder::PARAM_BOOL)))
+ ->andWhere($qb->expr()->eq('r.email', $qb->createNamedParameter($email)));
+ $result = $select->execute();
+ $cnt = $result->fetchColumn();
+ $result->closeCursor();
+ return (int)$cnt;
+ }
+
+ private function getNumberOfMessages(Mailbox $mb, string $email): int {
+ $qb = $this->db->getQueryBuilder();
+
+ $select = $qb->select($qb->func()->count('*'))
+ ->from('mail_recipients', 'r')
+ ->join('r', 'mail_messages', 'm', $qb->expr()->eq('m.id', 'r.message_id', IQueryBuilder::PARAM_INT))
+ ->join('r', 'mail_mailboxes', 'mb', $qb->expr()->eq('mb.id', $qb->expr()->castColumn('m.mailbox_id', IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT))
+ ->where($qb->expr()->eq('r.type', $qb->createNamedParameter(Address::TYPE_FROM), IQueryBuilder::PARAM_INT))
+ ->andWhere($qb->expr()->eq('mb.id', $qb->createNamedParameter($mb->getId(), IQueryBuilder::PARAM_INT)))
+ ->andWhere($qb->expr()->eq('r.email', $qb->createNamedParameter($email)));
+ $result = $select->execute();
+ $cnt = $result->fetchColumn();
+ $result->closeCursor();
+ return (int)$cnt;
+ }
+}
diff --git a/lib/Service/Classification/OftenRepliedSenderClassifier.php b/lib/Service/Classification/OftenRepliedSenderClassifier.php
new file mode 100644
index 0000000000..2f3f869270
--- /dev/null
+++ b/lib/Service/Classification/OftenRepliedSenderClassifier.php
@@ -0,0 +1,103 @@
+
+ *
+ * @author 2020 Christoph Wurst
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+namespace OCA\Mail\Service\Classification;
+
+use OCA\Mail\Account;
+use OCA\Mail\Address;
+use OCA\Mail\Db\Mailbox;
+use OCA\Mail\Db\MailboxMapper;
+use OCA\Mail\Db\Message;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+
+class OftenRepliedSenderClassifier extends AClassifier {
+ use SafeRatio;
+
+ /** @var MailboxMapper */
+ private $mailboxMapper;
+
+ /** @var IDBConnection */
+ private $db;
+
+ public function __construct(MailboxMapper $mailboxMapper,
+ IDBConnection $db) {
+ $this->mailboxMapper = $mailboxMapper;
+ $this->db = $db;
+ }
+
+ public function isImportant(Account $account, Mailbox $mailbox, Message $message): bool {
+ $sender = $message->getTo()->first();
+ if ($sender === null) {
+ return false;
+ }
+
+ try {
+ $mb = $this->mailboxMapper->findSpecial($account, 'inbox');
+ } catch (DoesNotExistException $e) {
+ return false;
+ }
+
+ return $this->greater(
+ $this->getNrOfRepliedMessages($mb, $sender->getEmail()),
+ $this->getNumberOfMessages($mb, $sender->getEmail()),
+ 0.1
+ );
+ }
+
+ private function getNrOfRepliedMessages(Mailbox $mb, string $email): int {
+ $qb = $this->db->getQueryBuilder();
+
+ $select = $qb->select($qb->func()->count('*'))
+ ->from('mail_recipients', 'r')
+ ->join('r', 'mail_messages', 'm', $qb->expr()->eq('m.id', 'r.message_id', IQueryBuilder::PARAM_INT))
+ ->join('r', 'mail_mailboxes', 'mb', $qb->expr()->eq('mb.id', $qb->expr()->castColumn('m.mailbox_id', IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT))
+ ->where($qb->expr()->eq('r.type', $qb->createNamedParameter(Address::TYPE_FROM), IQueryBuilder::PARAM_INT))
+ ->andWhere($qb->expr()->eq('mb.id', $qb->createNamedParameter($mb->getId(), IQueryBuilder::PARAM_INT)))
+ ->andWhere($qb->expr()->eq('m.flag_answered', $qb->createNamedParameter(true, IQueryBuilder::PARAM_BOOL)))
+ ->andWhere($qb->expr()->eq('r.email', $qb->createNamedParameter($email)));
+ $result = $select->execute();
+ $cnt = $result->fetchColumn();
+ $result->closeCursor();
+ return (int)$cnt;
+ }
+
+ private function getNumberOfMessages(Mailbox $mb, string $email): int {
+ $qb = $this->db->getQueryBuilder();
+
+ $select = $qb->select($qb->func()->count('*'))
+ ->from('mail_recipients', 'r')
+ ->join('r', 'mail_messages', 'm', $qb->expr()->eq('m.id', 'r.message_id', IQueryBuilder::PARAM_INT))
+ ->join('r', 'mail_mailboxes', 'mb', $qb->expr()->eq('mb.id', $qb->expr()->castColumn('m.mailbox_id', IQueryBuilder::PARAM_INT), IQueryBuilder::PARAM_INT))
+ ->where($qb->expr()->eq('r.type', $qb->createNamedParameter(Address::TYPE_FROM), IQueryBuilder::PARAM_INT))
+ ->andWhere($qb->expr()->eq('mb.id', $qb->createNamedParameter($mb->getId(), IQueryBuilder::PARAM_INT)))
+ ->andWhere($qb->expr()->eq('r.email', $qb->createNamedParameter($email)));
+ $result = $select->execute();
+ $cnt = $result->fetchColumn();
+ $result->closeCursor();
+ return (int)$cnt;
+ }
+}
diff --git a/src/components/Envelope.vue b/src/components/Envelope.vue
index 5b76fa32a7..89e465349d 100644
--- a/src/components/Envelope.vue
+++ b/src/components/Envelope.vue
@@ -1,5 +1,9 @@
-
+
+
@@ -32,6 +43,9 @@
{{
data.flags.flagged ? t('mail', 'Unfavorite') : t('mail', 'Favorite')
}}
+ {{
+ data.flags.important ? t('mail', 'Mark unimportant') : t('mail', 'Mark important')
+ }}
{{
data.flags.unseen ? t('mail', 'Mark read') : t('mail', 'Mark unread')
}}
@@ -66,6 +80,11 @@ export default {
required: true,
},
},
+ data() {
+ return {
+ importantSvg,
+ }
+ },
computed: {
accountColor() {
return calculateAccountColor(this.$store.getters.getAccount(this.data.accountId).emailAddress)
@@ -134,6 +153,9 @@ export default {
onToggleFlagged() {
this.$store.dispatch('toggleEnvelopeFlagged', this.data)
},
+ onToggleImportant() {
+ this.$store.dispatch('toggleEnvelopeImportant', this.data)
+ },
onToggleSeen() {
this.$store.dispatch('toggleEnvelopeSeen', this.data)
},
@@ -158,6 +180,18 @@ export default {
z-index: 1;
}
+.app-content-list-item-star.icon-important {
+ left: 7px;
+ top: 13px;
+ opacity: 1;
+ &:hover {
+ opacity: 0.5;
+ }
+ ::v-deep path {
+ fill: #ffcc00;
+ stroke: var(--color-main-background);
+ }
+}
.app-content-list-item.unseen {
font-weight: bold;
}
diff --git a/src/store/actions.js b/src/store/actions.js
index 12d586eb18..6063c9ab6d 100644
--- a/src/store/actions.js
+++ b/src/store/actions.js
@@ -493,6 +493,26 @@ export default {
})
})
},
+ toggleEnvelopeImportant({commit, getters}, envelope) {
+ // Change immediately and switch back on error
+ const oldState = envelope.flags.important
+ commit('flagEnvelope', {
+ envelope,
+ flag: 'important',
+ value: !oldState,
+ })
+
+ setEnvelopeFlag(envelope.accountId, envelope.folderId, envelope.id, 'important', !oldState).catch((e) => {
+ console.error('could not toggle message important state', e)
+
+ // Revert change
+ commit('flagEnvelope', {
+ envelope,
+ flag: 'important',
+ value: oldState,
+ })
+ })
+ },
toggleEnvelopeSeen({commit, getters}, envelope) {
// Change immediately and switch back on error
const oldState = envelope.flags.unseen