Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/mail.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMikhail Sazanov <m@sazanof.ru>2022-05-25 09:30:47 +0300
committerMikhail Sazanov <m@sazanof.ru>2022-05-25 09:30:47 +0300
commitbf265e025491c81f6484b2ea8c7fdf1253800090 (patch)
tree279f63f178f96b11f2aa75ab88b80b982d5370c3 /src
parentf267dbed3181b8e650e3dc591930ae66b4326679 (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.vue294
-rw-r--r--src/components/RecipientListItem.vue66
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>