diff --git a/appinfo/routes.php b/appinfo/routes.php index c868d1d316..25ab2aa6bf 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -105,6 +105,11 @@ 'url' => '/api/accounts/{id}/quota', 'verb' => 'GET' ], + [ + 'name' => 'accounts#testAccountConnection', + 'url' => '/api/accounts/{id}/test', + 'verb' => 'GET' + ], [ 'name' => 'autoConfig#queryIspdb', 'url' => '/api/autoconfig/ispdb/{email}', diff --git a/lib/Controller/AccountsController.php b/lib/Controller/AccountsController.php index b893474ac5..b3b37c8065 100644 --- a/lib/Controller/AccountsController.php +++ b/lib/Controller/AccountsController.php @@ -501,4 +501,19 @@ public function updateSmimeCertificate(int $id, ?int $smimeCertificateId = null) $this->accountService->update($account); return MailJsonResponse::success(); } + + /** + * @NoAdminRequired + * + * @param int $id Account id + * @return JSONResponse + * + * @throws ClientException + */ + public function testAccountConnection(int $id) { + return new JSONResponse([ + 'data' => $this->accountService->testAccountConnection($this->currentUserId, $id), + ]); + } + } diff --git a/lib/Service/AccountService.php b/lib/Service/AccountService.php index bca144d36b..537662a0e2 100644 --- a/lib/Service/AccountService.php +++ b/lib/Service/AccountService.php @@ -181,4 +181,21 @@ public function updateSignature(int $id, string $uid, string $signature = null): public function getAllAcounts(): array { return $this->mapper->getAllAccounts(); } + + + /** + * @param string $currentUserId + * @param int $accountId + * @return bool + */ + + public function testAccountConnection(string $currentUserId, int $accountId) :bool { + $account = $this->find($currentUserId, $accountId); + try { + $account->getImapConnection(); + return true; + } catch (ServiceException $e) { + return false; + } + } } diff --git a/src/components/Composer.vue b/src/components/Composer.vue index 67c7f1d648..84cb0a54d9 100644 --- a/src/components/Composer.vue +++ b/src/components/Composer.vue @@ -6,17 +6,27 @@ {{ t('mail', 'From') }}
- + :append-to-body="false" + :selectable="(option)=> { + return option.selectable}" + @option:selected="onAliasChange"> + + + +
@@ -419,7 +429,7 @@ import trimStart from 'lodash/fp/trimCharsStart' import Autosize from 'vue-autosize' import debouncePromise from 'debounce-promise' -import { NcActions as Actions, NcActionButton as ActionButton, NcActionCheckbox as ActionCheckbox, NcActionInput as ActionInput, NcActionRadio as ActionRadio, NcButton as ButtonVue, NcMultiselect as Multiselect, NcListItemIcon as ListItemIcon } from '@nextcloud/vue' +import { NcActions as Actions, NcActionButton as ActionButton, NcActionCheckbox as ActionCheckbox, NcActionInput as ActionInput, NcActionRadio as ActionRadio, NcButton as ButtonVue, NcMultiselect as Multiselect, NcSelect as Select, NcListItemIcon as ListItemIcon } from '@nextcloud/vue' import ChevronLeft from 'vue-material-design-icons/ChevronLeft' import Delete from 'vue-material-design-icons/Delete' import ComposerAttachments from './ComposerAttachments' @@ -480,6 +490,7 @@ export default { IconPublic, IconLinkPicker, Multiselect, + Select, TextEditor, ListItemIcon, RecipientListItem, @@ -587,6 +598,10 @@ export default { type: Boolean, default: false, }, + accounts: { + type: Array, + required: true, + }, }, data() { // Set default custom date time picker value to now + 1 hour @@ -649,7 +664,7 @@ export default { }, aliases() { let cnt = 0 - const accounts = this.$store.getters.accounts.filter((a) => !a.isUnified) + const accounts = this.accounts.filter((a) => !a.isUnified) const aliases = accounts.flatMap((account) => [ { id: account.id, @@ -661,6 +676,7 @@ export default { emailAddress: account.emailAddress, signatureAboveQuote: account.signatureAboveQuote, smimeCertificateId: account.smimeCertificateId, + selectable: account.connectionStatus, }, account.aliases.map((alias) => { return { @@ -673,6 +689,7 @@ export default { emailAddress: alias.alias, signatureAboveQuote: account.signatureAboveQuote, smimeCertificateId: alias.smimeCertificateId, + selectable: account.connectionStatus, } }), ]) @@ -1530,7 +1547,7 @@ export default { min-height: 100px; } -:deep(.multiselect .multiselect__tags), .subject { +:deep(.multiselect .multiselect__tags),:deep( .vs__dropdown-toggle),:deep(.vs__dropdown-menu), .subject { border: none !important; } :deep([data-select="create"] .avatardiv--unknown) { @@ -1540,6 +1557,23 @@ export default { flex-wrap: wrap; } +#from{ + width: 100%; + cursor: pointer; +} +:deep(.vs__actions){ + display: none; +} + +:deep(.vs__dropdown-menu){ + border: 1px solid var(--color-border) !important; + padding: 0 !important; + border-radius: 0 !important; +} + +:deep(.vs__dropdown-option){ + border-radius: 0 !important; +} .submit-message.send.primary.icon-confirm-white { color: var(--color-main-background); } diff --git a/src/components/NewMessageModal.vue b/src/components/NewMessageModal.vue index 1a49ceccda..d43eef0149 100644 --- a/src/components/NewMessageModal.vue +++ b/src/components/NewMessageModal.vue @@ -75,6 +75,7 @@ :smime-encrypt="composerData.smimeEncrypt" :is-first-open="modalFirstOpen" :request-mdn="composerData.requestMdn" + :accounts="accounts" @update:from-account="patchComposerData({ accountId: $event })" @update:from-alias="patchComposerData({ aliasId: $event })" @update:to="patchComposerData({ to: $event })" @@ -130,6 +131,12 @@ export default { NcActionButton, MinimizeIcon, }, + props: { + accounts: { + type: Array, + required: true, + }, + }, data() { return { toolbarElements: undefined, diff --git a/src/service/AccountService.js b/src/service/AccountService.js index df28630015..89784cc9d9 100644 --- a/src/service/AccountService.js +++ b/src/service/AccountService.js @@ -116,3 +116,12 @@ export const updateSmimeCertificate = async (id, smimeCertificateId) => { const response = await axios.put(url, { smimeCertificateId }) return response.data.data } + +export const testAccountConnection = async (id) => { + const url = generateUrl('/apps/mail/api/accounts/{id}/test', { + id, + }) + + const resp = await axios.get(url) + return resp.data.data +} diff --git a/src/tests/unit/components/Composer.vue.spec.js b/src/tests/unit/components/Composer.vue.spec.js index c7d3a23208..5307f583ed 100644 --- a/src/tests/unit/components/Composer.vue.spec.js +++ b/src/tests/unit/components/Composer.vue.spec.js @@ -68,6 +68,14 @@ describe('Composer', () => { propsData: { inReplyToMessageId: 'abc123', isFirstOpen: true, + accounts: [ + { + id: 123, + editorMode: 'plaintext', + isUnified: false, + aliases: [], + }, + ], }, store, localVue, @@ -83,6 +91,14 @@ describe('Composer', () => { propsData: { inReplyToMessageId: 'abc123', isFirstOpen: true, + accounts: [ + { + id: 123, + editorMode: 'plaintext', + isUnified: false, + aliases: [], + }, + ], }, store, localVue, @@ -101,6 +117,14 @@ describe('Composer', () => { { label: 'test', email: 'test@domain.tld' }, ], isFirstOpen: true, + accounts: [ + { + id: 123, + editorMode: 'plaintext', + isUnified: false, + aliases: [], + }, + ], }, store, localVue, @@ -115,6 +139,14 @@ describe('Composer', () => { const view = shallowMount(Composer, { propsData: { isFirstOpen: true, + accounts: [ + { + id: 123, + editorMode: 'plaintext', + isUnified: false, + aliases: [], + }, + ], }, computed: { smimeCertificateForCurrentAlias() { @@ -136,6 +168,14 @@ describe('Composer', () => { const view = shallowMount(Composer, { propsData: { isFirstOpen: true, + accounts: [ + { + id: 123, + editorMode: 'plaintext', + isUnified: false, + aliases: [], + }, + ], }, computed: { smimeCertificateForCurrentAlias() { @@ -157,6 +197,14 @@ describe('Composer', () => { const view = shallowMount(Composer, { propsData: { isFirstOpen: true, + accounts: [ + { + id: 123, + editorMode: 'plaintext', + isUnified: false, + aliases: [], + }, + ], }, computed: { smimeCertificateForCurrentAlias() { @@ -178,6 +226,14 @@ describe('Composer', () => { const view = shallowMount(Composer, { propsData: { isFirstOpen: true, + accounts: [ + { + id: 123, + editorMode: 'plaintext', + isUnified: false, + aliases: [], + }, + ], }, computed: { smimeCertificateForCurrentAlias() { @@ -202,6 +258,14 @@ describe('Composer', () => { const view = shallowMount(Composer, { propsData: { isFirstOpen: true, + accounts: [ + { + id: 123, + editorMode: 'plaintext', + isUnified: false, + aliases: [], + }, + ], }, computed: { smimeCertificateForCurrentAlias() { @@ -226,6 +290,14 @@ describe('Composer', () => { const view = shallowMount(Composer, { propsData: { isFirstOpen: true, + accounts: [ + { + id: 123, + editorMode: 'plaintext', + isUnified: false, + aliases: [], + }, + ], }, store, localVue, @@ -248,6 +320,14 @@ describe('Composer', () => { const view = shallowMount(Composer, { propsData: { isFirstOpen: true, + accounts: [ + { + id: 123, + editorMode: 'plaintext', + isUnified: false, + aliases: [], + }, + ], }, store, localVue, diff --git a/src/views/Home.vue b/src/views/Home.vue index 999fdce555..2a53eeac08 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -8,7 +8,7 @@ @@ -20,6 +20,7 @@ import isMobile from '@nextcloud/vue/dist/Mixins/isMobile' import '../../css/mail.scss' import '../../css/mobile.scss' +import { testAccountConnection } from '../service/AccountService' import logger from '../logger' import MailboxThread from '../components/MailboxThread' import Navigation from '../components/Navigation' @@ -41,6 +42,7 @@ export default { data() { return { hasComposerSession: false, + accounts: null, } }, computed: { @@ -74,6 +76,14 @@ export default { this.hasComposerSession = true }, }, + async beforeMount() { + const accounts = this.$store.getters.accounts.filter((a) => !a.isUnified) + this.accounts = await Promise.all( + accounts.map(async (account) => { + return { ...account, connectionStatus: await testAccountConnection(account.id) } + })) + + }, created() { const accounts = this.$store.getters.accounts let startMailboxId = this.$store.getters.getPreference('start-mailbox-id')