@@ -76,6 +76,8 @@ const Composer = forwardRef<
76
76
) => Promise < void >
77
77
removeFile : ( ) => void
78
78
clearDraftStateButKeepTextareaValue : ( ) => void
79
+ clearDraftStateAndUpdateTextareaValue : ( ) => void
80
+ setDraftStateAndUpdateTextareaValue : ( newValue : DraftObject ) => void
79
81
}
80
82
> ( ( props , ref ) => {
81
83
const {
@@ -210,49 +212,68 @@ const Composer = forwardRef<
210
212
if ( textareaRef ) {
211
213
if ( textareaRef . disabled ) {
212
214
throw new Error (
213
- 'text area is disabled, this means it is either already sending or loading the draft'
215
+ 'text area is disabled, this means it is loading the draft'
214
216
)
215
217
}
216
- textareaRef . disabled = true
217
218
}
218
- try {
219
- const message = regularMessageInputRef . current ?. getText ( ) || ''
220
- if ( ! regularMessageInputRef . current ?. hasText ( ) && ! draftState . file ) {
221
- log . debug ( `Empty message: don't send it...` )
222
- return
223
- }
224
-
225
- const sendMessagePromise = sendMessage ( accountId , chatId , {
226
- text : replaceColonsSafe ( message ) ,
227
- file : draftState . file || undefined ,
228
- filename : draftState . fileName || undefined ,
229
- quotedMessageId :
230
- draftState . quote ?. kind === 'WithMessage'
231
- ? draftState . quote . messageId
232
- : null ,
233
- viewtype : draftState . viewType ,
234
- } )
219
+ const message = regularMessageInputRef . current ?. getText ( ) || ''
220
+ if ( ! regularMessageInputRef . current ?. hasText ( ) && ! draftState . file ) {
221
+ log . debug ( `Empty message: don't send it...` )
222
+ return
223
+ }
235
224
225
+ const preSendDraftState = draftState
226
+ const sendMessagePromise = sendMessage ( accountId , chatId , {
227
+ text : replaceColonsSafe ( message ) ,
228
+ file : draftState . file || undefined ,
229
+ filename : draftState . fileName || undefined ,
230
+ quotedMessageId :
231
+ draftState . quote ?. kind === 'WithMessage'
232
+ ? draftState . quote . messageId
233
+ : null ,
234
+ viewtype : draftState . viewType ,
235
+ } )
236
+ // _Immediately_ clear the draft from React state.
237
+ // This does _not_ remove the draft from the back-end yet.
238
+ // This is primarily to make sure that you can't accidentally
239
+ // doube-send the same message.
240
+ //
241
+ // We could instead disable the textarea
242
+ // and disable sending the next message
243
+ // until the previous one has been sent,
244
+ // but it's unnecessary to block the user in such a way,
245
+ // because it's not often that `sendMessage` fails.
246
+ // And also disabling an input makes it lose focus,
247
+ // so we'd have to re-focus it, which would make screen readers
248
+ // re-announce it, which is disorienting.
249
+ // See https://github.com/deltachat/deltachat-desktop/issues/4590#issuecomment-2821985528.
250
+ props . clearDraftStateAndUpdateTextareaValue ( )
251
+
252
+ let sentSuccessfully : boolean
253
+ try {
236
254
await sendMessagePromise
237
-
238
- // Ensure that the draft is cleared
239
- // and the state is reflected in the UI.
240
- //
241
- // At this point we know that sending has succeeded,
242
- // so we do not accidentally remove the draft
243
- // if the core fails to send.
244
- await BackendRemote . rpc . removeDraft ( selectedAccountId ( ) , chatId )
245
- window . __reloadDraft && window . __reloadDraft ( )
246
- } catch ( error ) {
255
+ sentSuccessfully = true
256
+ } catch ( err ) {
257
+ sentSuccessfully = false
247
258
openDialog ( AlertDialog , {
248
- message : unknownErrorToString ( error ) ,
259
+ message :
260
+ tx ( 'systemmsg_failed_sending_to' , selectedChat . name ) +
261
+ '\n' +
262
+ tx ( 'error_x' , unknownErrorToString ( err ) ) ,
249
263
} )
250
- log . error ( error )
251
- } finally {
252
- if ( textareaRef ) {
253
- textareaRef . disabled = false
254
- }
255
- regularMessageInputRef . current ?. focus ( )
264
+ // Restore the draft, since we failed to send.
265
+ // Note that this will not save the draft to the backend.
266
+ //
267
+ // TODO fix: hypothetically by this point the user
268
+ // could have started typing a new message already,
269
+ // and so this would override it on the frontend.
270
+ props . setDraftStateAndUpdateTextareaValue ( preSendDraftState )
271
+ }
272
+ if ( sentSuccessfully ) {
273
+ // TODO fix: hypothetically by this point the user
274
+ // could have started typing (and even have sent!)
275
+ // a new message already, so this would override it on the backend.
276
+ await BackendRemote . rpc . removeDraft ( accountId , chatId )
256
277
}
257
278
}
258
279
@@ -723,6 +744,8 @@ export function useDraft(
723
744
) => Promise < void >
724
745
removeFile : ( ) => void
725
746
clearDraftStateButKeepTextareaValue : ( ) => void
747
+ clearDraftStateAndUpdateTextareaValue : ( ) => void
748
+ setDraftStateAndUpdateTextareaValue : ( newValue : DraftObject ) => void
726
749
} {
727
750
const [
728
751
draftState ,
@@ -748,6 +771,17 @@ export function useDraft(
748
771
const draftRef = useRef < DraftObject > ( emptyDraft ( chatId ) )
749
772
draftRef . current = draftState
750
773
774
+ /**
775
+ * @see {@link _setDraftStateButKeepTextareaValue }.
776
+ */
777
+ const setDraftStateAndUpdateTextareaValue = useCallback (
778
+ ( newValue : DraftObject ) => {
779
+ _setDraftStateButKeepTextareaValue ( newValue )
780
+ inputRef . current ?. setText ( newValue . text )
781
+ } ,
782
+ [ inputRef ]
783
+ )
784
+
751
785
/**
752
786
* Reset `draftState` to "empty draft" value,
753
787
* but don't save it to backend and don't change the value
@@ -756,6 +790,12 @@ export function useDraft(
756
790
const clearDraftStateButKeepTextareaValue = useCallback ( ( ) => {
757
791
_setDraftStateButKeepTextareaValue ( _ => emptyDraft ( chatId ) )
758
792
} , [ chatId ] )
793
+ /**
794
+ * @see {@link clearDraftStateButKeepTextareaValue }
795
+ */
796
+ const clearDraftStateAndUpdateTextareaValue = useCallback ( ( ) => {
797
+ setDraftStateAndUpdateTextareaValue ( emptyDraft ( chatId ) )
798
+ } , [ chatId , setDraftStateAndUpdateTextareaValue ] )
759
799
760
800
const loadDraft = useCallback (
761
801
( chatId : number ) => {
@@ -807,11 +847,6 @@ export function useDraft(
807
847
if ( chatId === null || ! canSend ) {
808
848
return
809
849
}
810
- if ( inputRef . current ?. textareaRef . current ?. disabled ) {
811
- // Guard against strange races
812
- log . warn ( 'Do not save draft while sending' )
813
- return
814
- }
815
850
const accountId = selectedAccountId ( )
816
851
817
852
const draft = draftRef . current
@@ -993,6 +1028,8 @@ export function useDraft(
993
1028
addFileToDraft,
994
1029
removeFile,
995
1030
clearDraftStateButKeepTextareaValue,
1031
+ clearDraftStateAndUpdateTextareaValue,
1032
+ setDraftStateAndUpdateTextareaValue,
996
1033
}
997
1034
}
998
1035
0 commit comments