diff options
author | Mikhail Sazanov <m@sazanof.ru> | 2022-05-25 09:30:47 +0300 |
---|---|---|
committer | Mikhail Sazanov <m@sazanof.ru> | 2022-05-25 09:30:47 +0300 |
commit | bf265e025491c81f6484b2ea8c7fdf1253800090 (patch) | |
tree | 279f63f178f96b11f2aa75ab88b80b982d5370c3 /src | |
parent | f267dbed3181b8e650e3dc591930ae66b4326679 (diff) |
Users can edit recipients list while writing a message
Signed-off-by: Mikhail Sazanov <m@sazanof.ru>
Diffstat (limited to 'src')
-rw-r--r-- | src/components/Composer.vue | 294 | ||||
-rw-r--r-- | src/components/RecipientListItem.vue | 66 |
2 files changed, 286 insertions, 74 deletions
diff --git a/src/components/Composer.vue b/src/components/Composer.vue index badc133c0..0ec1e8233 100644 --- a/src/components/Composer.vue +++ b/src/components/Composer.vue @@ -4,93 +4,149 @@ <label class="from-label" for="from"> {{ t('mail', 'From') }} </label> - <Multiselect - id="from" - :value="selectedAlias" - :options="aliases" - label="name" - track-by="selectId" - :searchable="false" - :custom-label="formatAliases" - :placeholder="t('mail', 'Select account')" - :clear-on-select="false" - @select="onAliasChange" /> + <div class="composer-fields--custom"> + <Multiselect + id="from" + :value="selectedAlias" + :options="aliases" + label="name" + track-by="selectId" + :searchable="false" + :custom-label="formatAliases" + :placeholder="t('mail', 'Select account')" + :clear-on-select="false" + @select="onAliasChange" /> + </div> </div> <div class="composer-fields"> <label class="to-label" for="to"> {{ t('mail', 'To') }} </label> - <Multiselect - id="to" - ref="toLabel" - v-model="selectTo" - :options="selectableRecipients" - :taggable="true" - label="label" - track-by="email" - :limit="4" - :multiple="true" - :placeholder="t('mail', 'Contact or email address …')" - :clear-on-select="true" - :close-on-select="false" - :show-no-options="false" - :preserve-search="true" - :hide-selected="true" - :loading="loadingIndicatorTo" - @input="callSaveDraft(true, getMessageData)" - @tag="onNewToAddr" - @search-change="onAutocomplete($event, 'to')" /> - <a v-if="!showCC" - class="copy-toggle" - href="#" - @click.prevent="showCC = true"> - {{ t('mail', '+ Cc/Bcc') }} - </a> + <div class="composer-fields--custom"> + <Multiselect id="to" + ref="toLabel" + v-model="selectTo" + :class="{'opened': !autoLimit}" + :options="selectableRecipients" + :taggable="true" + label="label" + track-by="email" + :multiple="true" + :placeholder="t('mail', 'Contact or email address …')" + :clear-on-select="true" + :close-on-select="false" + :show-no-options="false" + :preserve-search="true" + :hide-selected="true" + :loading="loadingIndicatorTo" + :auto-limit="autoLimit" + @input="callSaveDraft(true, getMessageData)" + @tag="onNewToAddr" + @search-change="onAutocomplete($event, 'to')"> + <template #tag="{ option }"> + <RecipientListItem + :option="option" + @remove-recipient="onRemoveRecipient(option, 'to')" /> + </template> + <template #option="{ option }"> + <div class="multiselect__tag multiselect__tag-custom"> + <ListItemIcon + :no-margin="true" + :title="option.label" + :subtitle="option.email" + :avatar-size="24" /> + </div> + </template> + </Multiselect> + <button + :title="t('mail','Toggle recipients list mode')" + :class="{'active':!autoLimit}" + @click.prevent="toggleViewMode"> + <UnfoldMoreHorizontal v-if="autoLimit" :size="24" /> + <UnfoldLessHorizontal v-else :size="24" /> + </button> + </div> </div> <div v-if="showCC" class="composer-fields"> <label for="cc" class="cc-label"> {{ t('mail', 'Cc') }} </label> - <Multiselect - id="cc" - v-model="selectCc" - :options="selectableRecipients" - :taggable="true" - label="label" - track-by="email" - :multiple="true" - :placeholder="t('mail', '')" - :clear-on-select="true" - :show-no-options="false" - :preserve-search="true" - :loading="loadingIndicatorCc" - @input="callSaveDraft(true, getMessageData)" - @tag="onNewCcAddr" - @search-change="onAutocomplete($event, 'cc')"> - <span slot="noOptions">{{ t('mail', 'No contacts found.') }}</span> - </Multiselect> + <div class="composer-fields--custom"> + <Multiselect id="cc" + v-model="selectCc" + :class="{'opened': !autoLimit}" + :options="selectableRecipients" + :taggable="true" + label="label" + track-by="email" + :multiple="true" + :placeholder="t('mail', 'Contact or email address …')" + :clear-on-select="true" + :show-no-options="false" + :preserve-search="true" + :loading="loadingIndicatorCc" + :auto-limit="autoLimit" + :hide-selected="true" + @input="callSaveDraft(true, getMessageData)" + @tag="onNewCcAddr" + @search-change="onAutocomplete($event, 'cc')"> + <template #tag="{ option }"> + <RecipientListItem + :option="option" + @remove-recipient="onRemoveRecipient(option, 'cc')" /> + </template> + <template #option="{ option }"> + <div class="multiselect__tag multiselect__tag-custom"> + <ListItemIcon + :no-margin="true" + :title="option.label" + :subtitle="option.email" + :avatar-size="24" /> + </div> + </template> + <span slot="noOptions">{{ t('mail', '') }}</span> + </Multiselect> + </div> </div> - <div v-if="showCC" class="composer-fields"> + <div v-if="showBCC" class="composer-fields"> <label for="bcc" class="bcc-label"> {{ t('mail', 'Bcc') }} </label> - <Multiselect - id="bcc" - v-model="selectBcc" - :options="selectableRecipients" - :taggable="true" - label="label" - track-by="email" - :multiple="true" - :placeholder="t('mail', '')" - :show-no-options="false" - :preserve-search="true" - :loading="loadingIndicatorBcc" - @input="callSaveDraft(true, getMessageData)" - @tag="onNewBccAddr" - @search-change="onAutocomplete($event, 'bcc')"> - <span slot="noOptions">{{ t('mail', 'No contacts found.') }}</span> - </Multiselect> + <div class="composer-fields--custom"> + <Multiselect id="bcc" + v-model="selectBcc" + :class="{'opened': !autoLimit}" + :options="selectableRecipients" + :taggable="true" + label="label" + track-by="email" + :multiple="true" + :placeholder="t('mail', 'Contact or email address …')" + :show-no-options="false" + :clear-on-select="true" + :preserve-search="true" + :loading="loadingIndicatorBcc" + :hide-selected="true" + @input="callSaveDraft(true, getMessageData)" + @tag="onNewBccAddr" + @search-change="onAutocomplete($event, 'bcc')"> + <template #tag="{ option }"> + <RecipientListItem + :option="option" + @remove-recipient="onRemoveRecipient(option, 'bcc')" /> + </template> + <template #option="{ option }"> + <div class="multiselect__tag multiselect__tag-custom"> + <ListItemIcon + :no-margin="true" + :title="option.label" + :subtitle="option.email" + :avatar-size="24" /> + </div> + </template> + <span slot="noOptions">{{ t('mail', 'No contacts found.') }}</span> + </Multiselect> + </div> </div> <div class="composer-fields"> <label for="subject" class="subject-label hidden-visually"> @@ -357,6 +413,10 @@ import ComposerAttachments from './ComposerAttachments' import Download from 'vue-material-design-icons/Download' import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent' import Multiselect from '@nextcloud/vue/dist/Components/Multiselect' +import ListItemIcon from '@nextcloud/vue/dist/Components/ListItemIcon' +import RecipientListItem from './RecipientListItem' +import UnfoldMoreHorizontal from 'vue-material-design-icons/UnfoldMoreHorizontal' +import UnfoldLessHorizontal from 'vue-material-design-icons/UnfoldLessHorizontal' import { showError } from '@nextcloud/dialogs' import { translate as t, getCanonicalLocale, getFirstDay, getLocale } from '@nextcloud/l10n' import Vue from 'vue' @@ -418,8 +478,12 @@ export default { Multiselect, TextEditor, EmptyContent, + ListItemIcon, + RecipientListItem, Send, SendClock, + UnfoldMoreHorizontal, + UnfoldLessHorizontal, }, props: { fromAccount: { @@ -496,6 +560,7 @@ export default { return { showCC: this.cc.length > 0, + showBCC: this.bcc.length > 0, selectedAlias: NO_ALIAS_SET, // Fixed in `beforeMount` autocompleteRecipients: this.to.concat(this.cc).concat(this.bcc), newRecipients: [], @@ -540,6 +605,7 @@ export default { return value ? moment(value, 'LLL').toDate() : null }, }, + autoLimit: true, editorRichInputTextReady: false, } }, @@ -1070,6 +1136,42 @@ export default { const minimumDate = new Date(now.getTime()) return date.getTime() <= minimumDate }, + /** + * Remove recipient from recipients array (To,Cc,Bcc) + * + * @param {Array} option Current option from Multiselect + * @param {Array} field List of recipients (ex. this.selectTo) + */ + onRemoveRecipient(option, field) { + switch (field) { + case 'to': + this.removeRecipientTo(option) + break + case 'cc': + this.removeRecipientCc(option) + break + case 'bcc': + this.removeRecipientBcc(option) + break + } + }, + removeRecipient(option, list) { + return list.filter((recipient) => recipient.email !== option.email) + }, + removeRecipientTo(option) { + this.selectTo = this.removeRecipient(option, this.selectTo) + }, + removeRecipientCc(option) { + this.selectCc = this.removeRecipient(option, this.selectCc) + }, + removeRecipientBcc(option) { + this.selectBcc = this.removeRecipient(option, this.selectBcc) + }, + toggleViewMode() { + this.autoLimit = !this.autoLimit + this.showCC = !(this.showCC && this.selectCc.length === 0 && this.autoLimit) + this.showBCC = !(this.showBCC && this.selectBcc.length === 0 && this.autoLimit) + }, }, } </script> @@ -1091,8 +1193,19 @@ export default { .composer-fields { display: flex; - align-items: center; border-top: 1px solid var(--color-border); + align-items: flex-start; + + .multiselect.multiselect--multiple::after { + position:absolute; + right: 0; + top: auto; + bottom:8px + } + + .multiselect__tag { + position: relative; + } &.mail-account { border-top: none; @@ -1113,6 +1226,29 @@ export default { border-radius: 0; } + .composer-fields--custom { + display: flex; + align-items: flex-start; + flex-wrap: wrap; + padding-top:5px; + width: calc(100% - 120px); + + button { + margin-top: 0; + background-color: transparent; + border:none; + opacity: 0.5 + } + + button.active,button:active { + opacity: 1; + } + + .multiselect { + width: calc(100% - 150px); + } + } + .multiselect { margin-right: 12px; } @@ -1171,6 +1307,7 @@ export default { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + font-weight: bold; } .bcc-label { @@ -1195,7 +1332,16 @@ export default { ::v-deep .multiselect .multiselect__tags { border: none !important; } +::v-deep [data-select="create"] .avatardiv--unknown { + background: var(--color-text-maxcontrast) !important; +} +::v-deep .multiselect.opened .multiselect__tags .multiselect__tags-wrap { + flex-wrap: wrap; +} +.submit-message.send.primary.icon-confirm-white { + color: var(--color-main-background); +} .sending-hint { height: 50px; margin-top: 50px; diff --git a/src/components/RecipientListItem.vue b/src/components/RecipientListItem.vue new file mode 100644 index 000000000..684f348e9 --- /dev/null +++ b/src/components/RecipientListItem.vue @@ -0,0 +1,66 @@ +<template> + <div class="multiselect__tag multiselect__tag--recipient"> + <ListItemIcon + :no-margin="true" + :title="option.label" + :avatar-size="24" /> + <Actions> + <ActionButton @click.prevent="removeRecipient(option)"> + <template #icon> + <Close :size="20" /> + </template> + </ActionButton> + </Actions> + </div> +</template> + +<script> +import ListItemIcon from '@nextcloud/vue/dist/Components/ListItemIcon' +import Actions from '@nextcloud/vue/dist/Components/Actions' +import ActionButton from '@nextcloud/vue/dist/Components/ActionButton' +import Close from 'vue-material-design-icons/Close' +export default { + name: 'RecipientListItem', + components: { + ListItemIcon, + Actions, + ActionButton, + Close, + }, + props: { + option: { + type: Object, + required: true, + }, + }, + methods: { + removeRecipient(option, field) { + this.$emit('remove-recipient', option, field) + }, + }, +} +</script> + +<style scoped> +.multiselect + .multiselect__tags + .multiselect__tags-wrap + .multiselect__tag--recipient { + padding: 0 25px 0 0; + border-radius: 25px; + border-color: transparent; + background-color: var(--color-background-dark); +} +.multiselect__tag--recipient .action-item--single { + width: auto; + min-width: 24px; + height: 24px; + min-height: 24px; + position: absolute; + right: 0; +} +.multiselect__tag--recipient .action-item--single .material-design-icon { + height: 24px; + width: 24px; +} +</style> |