147147 :quoted-text =" body"
148148 :is-reply-or-forward =" isReply || isForward" />
149149 </div >
150- <div class =" composer-actions" >
151- <ComposerAttachments v-model =" attachments"
152- :bus =" bus"
153- :upload-size-limit =" attachmentSizeLimit"
154- @upload =" onAttachmentsUploading" />
155- <div class =" composer-actions-right" >
156- <button v-if =" savingDraft === false"
150+ <ComposerAttachments v-model =" attachments"
151+ :bus =" bus"
152+ :upload-size-limit =" attachmentSizeLimit"
153+ @upload =" onAttachmentsUploading" />
154+ <div class =" composer-actions-right" >
155+ <div class =" composer-actions--primary-actions" >
156+ <p class =" composer-actions-draft-status" >
157+ <span v-if =" savingDraft === true" class =" draft-status" >{{ t('mail', 'Saving draft …') }}</span >
158+ <span v-else-if =" !canSaveDraft" class =" draft-status" >{{ t('mail', 'Error saving draft') }}</span >
159+ <span v-else-if =" savingDraft === false" class =" draft-status" >{{ t('mail', 'Draft saved') }}</span >
160+ </p >
161+ <VButton v-if =" !savingDraft && !canSaveDraft"
162+ class =" button"
163+ type =" tertiary"
164+ @click =" onSave" >
165+ <template #icon >
166+ <Download :size =" 20" :title =" t('mail', 'Save draft')" />
167+ </template >
168+ </VButton >
169+ <VButton v-if =" savingDraft === false"
157170 class =" button"
158- :title = " t('mail', 'Discard & close draft') "
171+ type = " tertiary "
159172 @click =" discardDraft" >
160- {{ t('mail', 'Discard draft') }}
161- </button >
162- <p class =" composer-actions-draft" >
163- <span v-if =" !canSaveDraft" id =" draft-status" >{{ t('mail', 'Cannot save draft because this account does not have a drafts mailbox configured.') }}</span >
164- <span v-else-if =" savingDraft === true" id =" draft-status" >{{ t('mail', 'Saving draft …') }}</span >
165- <span v-else-if =" savingDraft === false" id =" draft-status" >{{ t('mail', 'Draft saved') }}</span >
166- </p >
173+ <template #icon >
174+ <Delete :size =" 20" :title =" t('mail', 'Discard & close draft')" />
175+ </template >
176+ </VButton >
177+ </div >
178+ <div class =" composer-actions--secondary-actions" >
167179 <Actions >
168180 <template v-if =" ! isMoreActionsOpen " >
169181 <ActionButton icon =" icon-upload" @click =" onAddLocalAttachment" >
@@ -351,14 +363,17 @@ import ActionCheckbox from '@nextcloud/vue/dist/Components/ActionCheckbox'
351363import ActionInput from ' @nextcloud/vue/dist/Components/ActionInput'
352364import ActionLink from ' @nextcloud/vue/dist/Components/ActionLink'
353365import ActionRadio from ' @nextcloud/vue/dist/Components/ActionRadio'
366+ import Button from ' @nextcloud/vue/dist/Components/Button'
367+ import ChevronLeft from ' vue-material-design-icons/ChevronLeft'
368+ import Delete from ' vue-material-design-icons/Delete'
369+ import ComposerAttachments from ' ./ComposerAttachments'
370+ import Download from ' vue-material-design-icons/Download'
354371import EmptyContent from ' @nextcloud/vue/dist/Components/EmptyContent'
355372import Multiselect from ' @nextcloud/vue/dist/Components/Multiselect'
356373import { showError } from ' @nextcloud/dialogs'
357374import { translate as t , getCanonicalLocale , getFirstDay , getLocale } from ' @nextcloud/l10n'
358375import Vue from ' vue'
359376
360- import ComposerAttachments from ' ./ComposerAttachments'
361- import ChevronLeft from ' vue-material-design-icons/ChevronLeft'
362377import { findRecipient } from ' ../service/AutocompleteService'
363378import { detect , html , plain , toHtml , toPlain } from ' ../util/text'
364379import Loading from ' ./Loading'
@@ -375,6 +390,7 @@ import NoDraftsMailboxConfiguredError
375390 from ' ../errors/NoDraftsMailboxConfiguredError'
376391import ManyRecipientsError
377392 from ' ../errors/ManyRecipientsError'
393+
378394import Send from ' vue-material-design-icons/Send'
379395import SendClock from ' vue-material-design-icons/SendClock'
380396import moment from ' @nextcloud/moment'
@@ -407,8 +423,11 @@ export default {
407423 ActionInput,
408424 ActionLink,
409425 ActionRadio,
426+ VButton: Button,
410427 ComposerAttachments,
411428 ChevronLeft,
429+ Delete,
430+ Download,
412431 Loading,
413432 Multiselect,
414433 TextEditor,
@@ -823,27 +842,34 @@ export default {
823842 return uid
824843 })
825844 .catch (async (error) => {
826- console .error (' could not save draft' , error)
827- const canSave = await matchError (error, {
845+ await matchError (error, {
828846 [NoDraftsMailboxConfiguredError .getName ()]() {
829847 return false
830848 },
831849 default () {
832850 return true
833851 },
834852 })
835- if (! canSave) {
836- this .canSaveDraft = false
837- }
853+ this .canSaveDraft = false
838854 })
839855 .then ((uid ) => {
840856 this .savingDraft = false
841857 return uid
842858 })
843859 return this .draftsPromise
844860 },
861+ callSaveDraft (withDebounce , ... args ) {
862+ if (withDebounce) {
863+ return this .saveDraftDebounced (... args)
864+ } else {
865+ return this .saveDraft (... args)
866+ }
867+ },
868+ onSave () {
869+ this .callSaveDraft (false , this .getMessageData )
870+ },
845871 onInputChanged () {
846- this .saveDraftDebounced ( this .getMessageData )
872+ this .callSaveDraft ( true , this .getMessageData )
847873 if (this .appendSignature ) {
848874 const signatureValue = toHtml (detect (this .selectedAlias .signature )).value
849875 this .bus .$emit (' insertSignature' , signatureValue, this .selectedAlias .signatureAboveQuote )
@@ -1016,8 +1042,13 @@ export default {
10161042 },
10171043 async discardDraft () {
10181044 this .state = STATES .DISCARDING
1019- const id = await this .draftsPromise
1020- await this .$store .dispatch (' deleteMessage' , { id })
1045+ let id
1046+ try {
1047+ id = await this .draftsPromise
1048+ await this .$store .dispatch (' deleteMessage' , { id })
1049+ } catch (err) {
1050+ logger .error (' Could not delete message with id ' + id)
1051+ }
10211052 this .state = STATES .DISCARDED
10221053 this .$emit (' close' )
10231054 },
@@ -1060,19 +1091,14 @@ export default {
10601091.composer - actions {
10611092 display: flex;
10621093 flex- direction: row;
1063- align- items: flex - end ;
1094+ align- items: center ;
10641095 justify- content: space- between;
10651096 position: sticky;
10661097 bottom: 0 ;
10671098 padding: 12px ;
10681099 background: linear- gradient (rgba (255 , 255 , 255 , 0 ), var (-- color- main- background- translucent) 50 % );
10691100}
10701101
1071- .composer - actions- right {
1072- display: flex;
1073- align- items: center;
1074- }
1075-
10761102.composer - fields {
10771103 display: flex;
10781104 align- items: center;
@@ -1126,10 +1152,11 @@ export default {
11261152 padding- left: 20px ;
11271153}
11281154
1129- # draft- status {
1155+ . draft - status {
11301156 padding: 5px ;
11311157 opacity: 0.5 ;
11321158 font- size: small;
1159+ display: block;
11331160}
11341161
11351162.from - label,
@@ -1196,4 +1223,47 @@ export default {
11961223.send - button .send - icon {
11971224 padding- right: 5px ;
11981225}
1226+ .composer - actions- right {
1227+ display: flex;
1228+ align- items: center;
1229+ flex- direction: row;
1230+ justify- content: space- between;
1231+ padding- bottom: 10px ;
1232+ }
1233+ .composer - actions-- primary- actions {
1234+ display: flex;
1235+ flex- direction: row;
1236+ padding- left: 10px ;
1237+ align- items: center;
1238+ }
1239+ .composer - actions-- primary- actions .button {
1240+ padding: 5px ;
1241+ }
1242+ .composer - actions-- secondary- actions {
1243+ display: flex;
1244+ flex- direction: row;
1245+ padding- right: 15px ;
1246+ }
1247+ .composer - actions-- secondary- actions .button {
1248+ flex- shrink: 0 ;
1249+ }
1250+
1251+ .composer - actions- draft- status {
1252+ padding- left: 15px ;
1253+ }
1254+
1255+ @media only screen and (max - width : 580px ) {
1256+ .composer - actions- right {
1257+ align- items: end;
1258+ flex- direction: column- reverse;
1259+ }
1260+ .composer - actions- draft- status {
1261+ text- align: end;
1262+ padding- right: 15px ;
1263+ }
1264+ .composer - actions-- primary- actions {
1265+ padding- right: 5px ;
1266+ }
1267+ }
1268+
11991269< / style>
0 commit comments