From 964881927724d1267ae4ff7624a5d29df154b502 Mon Sep 17 00:00:00 2001 From: "julia.kirschenheuter" Date: Tue, 12 Apr 2022 10:34:33 +0200 Subject: [PATCH] Allow possibility to choose a time for scheduled sending Add few action buttons for pre-defined time to send. Add a new submenu for scheduled time. Compute a time to send as a timestamp. Add disabled parameters to Datetimepicker. Add localisation to Datetimepicker. Dispatch sendMessage only if sendAt is not available. Signed-off-by: julia.kirschenheuter --- src/components/Composer.vue | 254 ++++++++++++++++++++++++----- src/components/NewMessageModal.vue | 56 +++---- src/components/OutboxComposer.vue | 0 3 files changed, 235 insertions(+), 75 deletions(-) create mode 100644 src/components/OutboxComposer.vue diff --git a/src/components/Composer.vue b/src/components/Composer.vue index 1472b84d90..5c6c39c542 100644 --- a/src/components/Composer.vue +++ b/src/components/Composer.vue @@ -165,49 +165,125 @@ {{ t('mail', 'Draft saved') }}

- - {{ - t('mail', 'Upload attachment') - }} - - - {{ - t('mail', 'Add attachment from Files') - }} - - - {{ - addShareLink - }} - - - {{ t('mail', 'Enable formatting') }} - - - {{ t('mail', 'Request a read receipt') }} - - - {{ t('mail', 'Encrypt message with Mailvelope') }} - - - {{ - t('mail', 'Looking for a way to encrypt your emails? Install the Mailvelope browser extension!') - }} - + +
{ + return date ? moment(date).format('LLL') : '' + }, + }, } }, computed: { @@ -478,12 +573,40 @@ export default { return this.editorMode === 'plaintext' }, submitButtonTitle() { + if (this.sendAt) { + return t('mail', 'Send later') + ` ${this.convertToLocalDate(this.sendAt)}` + } if (!this.mailvelope.available) { return t('mail', 'Send') } return this.encrypt ? t('mail', 'Encrypt and send') : t('mail', 'Send unencrypted') }, + dateTomorrowMorning() { + const today = new Date() + today.setTime(today.getTime() + 24 * 60 * 60 * 1000) + return today.setHours(9, 0, 0, 0) + + }, + dateTomorrowAfternoon() { + const today = new Date() + today.setTime(today.getTime() + 24 * 60 * 60 * 1000) + return today.setHours(14, 0, 0, 0) + }, + dateMondayMorning() { + const today = new Date() + today.setHours(9, 0, 0, 0) + return today.setDate(today.getDate() + (7 - today.getDay()) % 7 + 1) + }, + customSendTime() { + return new Date(this.selectedDate).getTime() + }, + showAmPm() { + const localeData = moment().locale(getLocale()).localeData() + const timeFormat = localeData.longDateFormat('LT').toLowerCase() + + return timeFormat.indexOf('a') !== -1 + }, }, watch: { '$route.params.threadId'() { @@ -619,6 +742,7 @@ export default { messageId: this.replyTo ? this.replyTo.databaseId : undefined, isHtml: !this.editorPlainText, requestMdn: this.requestMdn, + sendAt: this.sendAt ? Math.floor(this.sendAt / 1000) : undefined, } }, saveDraft(data) { @@ -677,6 +801,19 @@ export default { this.appendSignature = false } }, + onChangeSendLater(value, isCustomSendTime = false) { + this.isCustomSendTime = isCustomSendTime + this.sendAt = value ? Number.parseInt(value, 10) : undefined + }, + convertToLocalDate(timestamp) { + const options = { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + } + return new Date(timestamp).toLocaleString(getCanonicalLocale(), options) + }, onAliasChange(alias) { logger.debug('changed alias', { alias }) this.selectedAlias = alias @@ -835,6 +972,32 @@ export default { this.state = STATES.DISCARDED this.$emit('close') }, + /** + * Whether the date is acceptable + * + * @param {Date} date The date to compare to + * @returns {boolean} + */ + disabledDatetimepickerDate(date) { + const minimumDate = new Date() + // Make it one sec before midnight so it shows the next full day as available + minimumDate.setHours(0, 0, 0) + minimumDate.setSeconds(minimumDate.getSeconds() - 1) + + return date.getTime() <= minimumDate + }, + + /** + * Whether the time for date is acceptable + * + * @param {Date} date The date to compare to + * @returns {boolean} + */ + disabledDatetimepickerTime(date) { + const now = new Date() + const minimumDate = new Date(now.getTime()) + return date.getTime() <= minimumDate + }, }, } @@ -979,4 +1142,7 @@ export default { margin-top: 250px; height: 120px; } +.send-action-radio { + padding: 5px 0 5px 0; +} diff --git a/src/components/NewMessageModal.vue b/src/components/NewMessageModal.vue index 7e0dc9a91e..d5d0042888 100644 --- a/src/components/NewMessageModal.vue +++ b/src/components/NewMessageModal.vue @@ -97,47 +97,41 @@ export default { }, async sendMessage(data) { logger.debug('sending message', { data }) + const now = new Date().getTime() + const dataForServer = { + accountId: data.account, + subject: data.subject, + body: data.isHtml ? data.body.value : toPlain(data.body).value, + isHtml: data.isHtml, + to: data.to, + cc: data.cc, + bcc: data.bcc, + attachments: data.attachments, + aliasId: null, + inReplyToMessageId: null, + sendAt: data.sendAt ? data.sendAt : Math.floor(now / 1000), + } + if (dataForServer.sendAt < Math.floor(now / 1000)) { + dataForServer.sendAt = Math.floor(now / 1000) + } if (this.composerMessage.type === 'outbox') { - const now = new Date().getTime() - const dataForServer = { - accountId: data.account, - subject: data.subject, - body: data.isHtml ? data.body.value : toPlain(data.body).value, - isHtml: data.isHtml, - to: data.to, - cc: data.cc, - bcc: data.bcc, - attachments: data.attachments, - aliasId: data.aliasId, - inReplyToMessageId: null, - sendAt: Math.floor(now / 1000), // JS timestamp is in milliseconds - } - const message = await this.$store.dispatch('outbox/updateMessage', { + // TODO: update the message instead of enqueing another time + const message = await this.$store.dispatch('outbox/enqueueMessage', { message: dataForServer, id: this.composerData.id, }) + if (!data.sendAt) { + await this.$store.dispatch('outbox/sendMessage', { id: message.id }) + } - await this.$store.dispatch('outbox/sendMessage', { id: message.id }) } else { - const now = new Date().getTime() - const dataForServer = { - accountId: data.account, - subject: data.subject, - body: data.isHtml ? data.body.value : toPlain(data.body).value, - isHtml: data.isHtml, - to: data.to, - cc: data.cc, - bcc: data.bcc, - attachments: data.attachments, - aliasId: data.aliasId, - inReplyToMessageId: null, - sendAt: Math.floor(now / 1000), // JS timestamp is in milliseconds - } const message = await this.$store.dispatch('outbox/enqueueMessage', { message: dataForServer, }) - await this.$store.dispatch('outbox/sendMessage', { id: message.id }) + if (!data.sendAt) { + await this.$store.dispatch('outbox/sendMessage', { id: message.id }) + } if (data.draftId) { // Remove old draft envelope diff --git a/src/components/OutboxComposer.vue b/src/components/OutboxComposer.vue new file mode 100644 index 0000000000..e69de29bb2