@@ -26,6 +26,7 @@ import {
26
26
} from "matrix-js-sdk/src/matrix" ;
27
27
import { Optional } from "matrix-events-sdk" ;
28
28
import { Tooltip } from "@vector-im/compound-web" ;
29
+ import { logger } from "matrix-js-sdk/src/logger" ;
29
30
30
31
import { _t } from "../../../languageHandler" ;
31
32
import { MatrixClientPeg } from "../../../MatrixClientPeg" ;
@@ -65,6 +66,9 @@ import { createCantStartVoiceMessageBroadcastDialog } from "../dialogs/CantStart
65
66
import { UIFeature } from "../../../settings/UIFeature" ;
66
67
import { formatTimeLeft } from "../../../DateUtils" ;
67
68
69
+ // The prefix used when persisting editor drafts to localstorage.
70
+ export const WYSIWYG_EDITOR_STATE_STORAGE_PREFIX = "mx_wysiwyg_state_" ;
71
+
68
72
let instanceCount = 0 ;
69
73
70
74
interface ISendButtonProps {
@@ -109,6 +113,12 @@ interface IState {
109
113
initialComposerContent : string ;
110
114
}
111
115
116
+ type WysiwygComposerState = {
117
+ content : string ;
118
+ isRichText : boolean ;
119
+ replyEventId ?: string ;
120
+ } ;
121
+
112
122
export class MessageComposer extends React . Component < IProps , IState > {
113
123
private dispatcherRef ?: string ;
114
124
private messageComposerInput = createRef < SendMessageComposerClass > ( ) ;
@@ -129,21 +139,42 @@ export class MessageComposer extends React.Component<IProps, IState> {
129
139
130
140
public constructor ( props : IProps , context : React . ContextType < typeof RoomContext > ) {
131
141
super ( props , context ) ;
142
+ this . context = context ; // otherwise React will only set it prior to render due to type def above
143
+
132
144
VoiceRecordingStore . instance . on ( UPDATE_EVENT , this . onVoiceStoreUpdate ) ;
133
145
146
+ window . addEventListener ( "beforeunload" , this . saveWysiwygEditorState ) ;
147
+ const isWysiwygLabEnabled = SettingsStore . getValue < boolean > ( "feature_wysiwyg_composer" ) ;
148
+ let isRichTextEnabled = true ;
149
+ let initialComposerContent = "" ;
150
+ if ( isWysiwygLabEnabled ) {
151
+ const wysiwygState = this . restoreWysiwygEditorState ( ) ;
152
+ if ( wysiwygState ) {
153
+ isRichTextEnabled = wysiwygState . isRichText ;
154
+ initialComposerContent = wysiwygState . content ;
155
+ if ( wysiwygState . replyEventId ) {
156
+ dis . dispatch ( {
157
+ action : "reply_to_event" ,
158
+ event : this . props . room . findEventById ( wysiwygState . replyEventId ) ,
159
+ context : this . context . timelineRenderingType ,
160
+ } ) ;
161
+ }
162
+ }
163
+ }
164
+
134
165
this . state = {
135
- isComposerEmpty : true ,
136
- composerContent : "" ,
166
+ isComposerEmpty : initialComposerContent ?. length === 0 ,
167
+ composerContent : initialComposerContent ,
137
168
haveRecording : false ,
138
169
recordingTimeLeftSeconds : undefined , // when set to a number, shows a toast
139
170
isMenuOpen : false ,
140
171
isStickerPickerOpen : false ,
141
172
showStickersButton : SettingsStore . getValue ( "MessageComposerInput.showStickersButton" ) ,
142
173
showPollsButton : SettingsStore . getValue ( "MessageComposerInput.showPollsButton" ) ,
143
174
showVoiceBroadcastButton : SettingsStore . getValue ( Features . VoiceBroadcast ) ,
144
- isWysiwygLabEnabled : SettingsStore . getValue < boolean > ( "feature_wysiwyg_composer" ) ,
145
- isRichTextEnabled : true ,
146
- initialComposerContent : "" ,
175
+ isWysiwygLabEnabled : isWysiwygLabEnabled ,
176
+ isRichTextEnabled : isRichTextEnabled ,
177
+ initialComposerContent : initialComposerContent ,
147
178
} ;
148
179
149
180
this . instanceId = instanceCount ++ ;
@@ -154,6 +185,52 @@ export class MessageComposer extends React.Component<IProps, IState> {
154
185
SettingsStore . monitorSetting ( "feature_wysiwyg_composer" , null ) ;
155
186
}
156
187
188
+ private get editorStateKey ( ) : string {
189
+ let key = WYSIWYG_EDITOR_STATE_STORAGE_PREFIX + this . props . room . roomId ;
190
+ if ( this . props . relation ?. rel_type === THREAD_RELATION_TYPE . name ) {
191
+ key += `_${ this . props . relation . event_id } ` ;
192
+ }
193
+ return key ;
194
+ }
195
+
196
+ private restoreWysiwygEditorState ( ) : WysiwygComposerState | undefined {
197
+ const json = localStorage . getItem ( this . editorStateKey ) ;
198
+ if ( json ) {
199
+ try {
200
+ const state : WysiwygComposerState = JSON . parse ( json ) ;
201
+ return state ;
202
+ } catch ( e ) {
203
+ logger . error ( e ) ;
204
+ }
205
+ }
206
+ return undefined ;
207
+ }
208
+
209
+ private saveWysiwygEditorState = ( ) : void => {
210
+ if ( this . shouldSaveWysiwygEditorState ( ) ) {
211
+ const { isRichTextEnabled, composerContent } = this . state ;
212
+ const replyEventId = this . props . replyToEvent ? this . props . replyToEvent . getId ( ) : undefined ;
213
+ const item : WysiwygComposerState = {
214
+ content : composerContent ,
215
+ isRichText : isRichTextEnabled ,
216
+ replyEventId : replyEventId ,
217
+ } ;
218
+ localStorage . setItem ( this . editorStateKey , JSON . stringify ( item ) ) ;
219
+ } else {
220
+ this . clearStoredEditorState ( ) ;
221
+ }
222
+ } ;
223
+
224
+ // should save state when wysiwyg is enabled and has contents or reply is open
225
+ private shouldSaveWysiwygEditorState = ( ) : boolean => {
226
+ const { isWysiwygLabEnabled, isComposerEmpty } = this . state ;
227
+ return isWysiwygLabEnabled && ( ! isComposerEmpty || ! ! this . props . replyToEvent ) ;
228
+ } ;
229
+
230
+ private clearStoredEditorState ( ) : void {
231
+ localStorage . removeItem ( this . editorStateKey ) ;
232
+ }
233
+
157
234
private get voiceRecording ( ) : Optional < VoiceMessageRecording > {
158
235
return this . _voiceRecording ;
159
236
}
@@ -265,6 +342,8 @@ export class MessageComposer extends React.Component<IProps, IState> {
265
342
UIStore . instance . stopTrackingElementDimensions ( `MessageComposer${ this . instanceId } ` ) ;
266
343
UIStore . instance . removeListener ( `MessageComposer${ this . instanceId } ` , this . onResize ) ;
267
344
345
+ window . removeEventListener ( "beforeunload" , this . saveWysiwygEditorState ) ;
346
+ this . saveWysiwygEditorState ( ) ;
268
347
// clean up our listeners by setting our cached recording to falsy (see internal setter)
269
348
this . voiceRecording = null ;
270
349
}
0 commit comments