diff --git a/lib/Contracts/IMailManager.php b/lib/Contracts/IMailManager.php index 9c470412b3..e3b4b6a5bf 100644 --- a/lib/Contracts/IMailManager.php +++ b/lib/Contracts/IMailManager.php @@ -196,4 +196,17 @@ public function renameMailbox(Account $account, Mailbox $mailbox, string $name): * @throws ServiceException */ public function deleteMailbox(Account $account, Mailbox $mailbox): void; + + /** + * @param Account $account + * @param string $mailbox + * @param bool $subscribed + * + * @return Mailbox + * @throws ClientException + * @throws ServiceException + */ + public function updateSubscription(Account $account, + Mailbox $mailbox, + bool $subscribed): Mailbox; } diff --git a/lib/Controller/MailboxesController.php b/lib/Controller/MailboxesController.php index 4a37192856..b70c66d707 100644 --- a/lib/Controller/MailboxesController.php +++ b/lib/Controller/MailboxesController.php @@ -108,7 +108,8 @@ public function index(int $accountId): JSONResponse { * @return JSONResponse */ public function patch(int $id, - ?string $name = null): JSONResponse { + ?string $name = null, + ?bool $subscribed = null): JSONResponse { $mailbox = $this->mailManager->getMailbox($this->currentUserId, $id); $account = $this->accountService->find($this->currentUserId, $mailbox->getAccountId()); @@ -119,6 +120,13 @@ public function patch(int $id, $name ); } + if ($subscribed !== null) { + $mailbox = $this->mailManager->updateSubscription( + $account, + $mailbox, + $subscribed + ); + } return new JSONResponse($mailbox); } diff --git a/lib/IMAP/MailboxSync.php b/lib/IMAP/MailboxSync.php index baeadae882..c059ec327d 100644 --- a/lib/IMAP/MailboxSync.php +++ b/lib/IMAP/MailboxSync.php @@ -148,6 +148,18 @@ private function getPersonalNamespace(Horde_Imap_Client_Namespace_List $namespac return null; } + /** + * @param Folder $folder + * @param Mailbox $mailbox + * + * @return string + */ + public function updateMailboxAttributes(Folder $folder, Mailbox $mailbox) : string { + $attributes = $folder->getAttributes(); + $mailbox->setAttributes(json_encode($attributes)); + return $this->mailboxMapper->update($mailbox)->getAttributes(); + } + private function updateMailboxFromFolder(Folder $folder, Mailbox $mailbox): void { $mailbox->setDelimiter($folder->getDelimiter()); $mailbox->setAttributes(json_encode($folder->getAttributes())); diff --git a/lib/Service/MailManager.php b/lib/Service/MailManager.php index 7d417bec5a..8d106cd5a9 100644 --- a/lib/Service/MailManager.php +++ b/lib/Service/MailManager.php @@ -310,6 +310,28 @@ public function markFolderAsRead(Account $account, Mailbox $mailbox): void { $this->imapMessageMapper->markAllRead($client, $mailbox->getName()); } + public function updateSubscription(Account $account, Mailbox $mailbox, bool $subscribed): Mailbox { + /** + * 1. Change subscription on IMAP + */ + $client = $this->imapClientFactory->getClient($account); + try { + $client->subscribeMailbox($mailbox->getName(), $subscribed); + } catch (Horde_Imap_Client_Exception $e) { + throw new ServiceException("Could not set subscription status for mailbox $mailbox on IMAP: " . $e->getMessage(), $e->getCode(), $e); + } + + /** + * Pull changes into the mailbox database cache + */ + $this->mailboxSync->sync($account, true); + + /** + * Return the updated object + */ + return $this->mailboxMapper->find($account, $mailbox->getName()); + } + public function flagMessage(Account $account, string $mailbox, int $uid, string $flag, bool $value): void { $client = $this->imapClientFactory->getClient($account); try { diff --git a/src/components/NavigationMailbox.vue b/src/components/NavigationMailbox.vue index 49c07416e2..82354408a2 100644 --- a/src/components/NavigationMailbox.vue +++ b/src/components/NavigationMailbox.vue @@ -80,6 +80,15 @@ @click="clearCache"> {{ t('mail', 'Clear locally cached data, in case there are issues with synchronization.') }} + + + {{ t('mail', 'Subscribed') }} + + {{ t('mail', 'Delete folder') }} @@ -103,6 +112,7 @@ import AppNavigationItem from '@nextcloud/vue/dist/Components/AppNavigationItem' import AppNavigationCounter from '@nextcloud/vue/dist/Components/AppNavigationCounter' import ActionButton from '@nextcloud/vue/dist/Components/ActionButton' +import ActionCheckbox from '@nextcloud/vue/dist/Components/ActionCheckbox' import ActionInput from '@nextcloud/vue/dist/Components/ActionInput' import ActionText from '@nextcloud/vue/dist/Components/ActionText' @@ -120,6 +130,7 @@ export default { AppNavigationCounter, ActionText, ActionButton, + ActionCheckbox, ActionInput, }, props: { @@ -148,6 +159,7 @@ export default { loadingMarkAsRead: false, clearingCache: false, showSaving: false, + changeSubscription: false, editing: false, showSubMailboxes: false, menuOpen: false, @@ -164,6 +176,9 @@ export default { || (this.mailbox.attributes && this.mailbox.attributes.includes('\\subscribed')) ) }, + notVirtual() { + return !this.account.isUnified && this.mailbox.specialRole !== 'flagged' + }, title() { if (this.filter === 'starred') { // Little hack to trick the translation logic into a different path @@ -218,6 +233,9 @@ export default { } return t('mail', 'Loading …') }, + isSubscribed() { + return this.mailbox.attributes && this.mailbox.attributes.includes('\\subscribed') + }, }, methods: { /** @@ -295,6 +313,21 @@ export default { .catch((error) => logger.error(`could not mark mailbox ${this.mailbox.databaseId} as read`, { error })) .then(() => (this.loadingMarkAsRead = false)) }, + async changeFolderSubscription(subscribed) { + try { + this.changeSubscription = true + + await this.$store.dispatch('changeMailboxSubscription', { + mailbox: this.mailbox, + subscribed, + }) + } catch (error) { + logger.error(`could not update subscription of mailbox ${mailbox.databaseId}`, { error }) + throw error + } finally { + this.changeSubscription = false + } + }, async clearCache() { try { this.clearingCache = true diff --git a/src/store/actions.js b/src/store/actions.js index 629cbca83a..76da406e66 100644 --- a/src/store/actions.js +++ b/src/store/actions.js @@ -212,6 +212,21 @@ export default { }) ) }, + async changeMailboxSubscription({ commit }, { mailbox, subscribed }) { + logger.debug(`toggle subscription for mailbox ${mailbox.databaseId}`, { + mailbox, + subscribed, + }) + const updated = await patchMailbox(mailbox.databaseId, { subscribed }) + + commit('updateMailbox', { + mailbox: updated, + }) + logger.debug(`subscription for mailbox ${mailbox.databaseId} updated`, { + mailbox, + updated, + }) + }, fetchEnvelope({ commit, getters }, id) { const cached = getters.getEnvelope(id) if (cached) { diff --git a/src/store/mutations.js b/src/store/mutations.js index abfa075cad..0e893d0f9f 100644 --- a/src/store/mutations.js +++ b/src/store/mutations.js @@ -113,6 +113,9 @@ export default { addMailbox(state, { account, mailbox }) { addMailboxToState(state, account, mailbox) }, + updateMailbox(state, { mailbox }) { + Vue.set(state.mailboxes, mailbox.databaseId, mailbox) + }, removeMailbox(state, { id }) { const mailbox = state.mailboxes[id] if (mailbox === undefined) {