@@ -33,6 +33,80 @@ import {MatrixClient} from 'matrix-js-sdk';
33
33
import classNames from 'classnames' ;
34
34
import { EventStatus } from 'matrix-js-sdk' ;
35
35
36
+ function _isReply ( mxEvent ) {
37
+ const relatesTo = mxEvent . getContent ( ) [ "m.relates_to" ] ;
38
+ const isReply = ! ! ( relatesTo && relatesTo [ "m.in_reply_to" ] ) ;
39
+ return isReply ;
40
+ }
41
+
42
+ function getHtmlReplyFallback ( mxEvent ) {
43
+ const html = mxEvent . getContent ( ) . formatted_body ;
44
+ if ( ! html ) {
45
+ return "" ;
46
+ }
47
+ const rootNode = new DOMParser ( ) . parseFromString ( html , "text/html" ) . body ;
48
+ const mxReply = rootNode . querySelector ( "mx-reply" ) ;
49
+ return ( mxReply && mxReply . outerHTML ) || "" ;
50
+ }
51
+
52
+ function getTextReplyFallback ( mxEvent ) {
53
+ const body = mxEvent . getContent ( ) . body ;
54
+ const lines = body . split ( "\n" ) . map ( l => l . trim ( ) ) ;
55
+ if ( lines . length > 2 && lines [ 0 ] . startsWith ( "> " ) && lines [ 1 ] . length === 0 ) {
56
+ return `${ lines [ 0 ] } \n\n` ;
57
+ }
58
+ return "" ;
59
+ }
60
+
61
+ function _isEmote ( model ) {
62
+ const firstPart = model . parts [ 0 ] ;
63
+ return firstPart && firstPart . type === "plain" && firstPart . text . startsWith ( "/me " ) ;
64
+ }
65
+
66
+ function createEditContent ( model , editedEvent ) {
67
+ const isEmote = _isEmote ( model ) ;
68
+ if ( isEmote ) {
69
+ // trim "/me "
70
+ model = model . clone ( ) ;
71
+ model . removeText ( { index : 0 , offset : 0 } , 4 ) ;
72
+ }
73
+ const isReply = _isReply ( editedEvent ) ;
74
+ let plainPrefix = "" ;
75
+ let htmlPrefix = "" ;
76
+
77
+ if ( isReply ) {
78
+ plainPrefix = getTextReplyFallback ( editedEvent ) ;
79
+ htmlPrefix = getHtmlReplyFallback ( editedEvent ) ;
80
+ }
81
+
82
+ const body = textSerialize ( model ) ;
83
+
84
+ const newContent = {
85
+ "msgtype" : isEmote ? "m.emote" : "m.text" ,
86
+ "body" : plainPrefix + body ,
87
+ } ;
88
+ const contentBody = {
89
+ msgtype : newContent . msgtype ,
90
+ body : `${ plainPrefix } * ${ body } ` ,
91
+ } ;
92
+
93
+ const formattedBody = htmlSerializeIfNeeded ( model , { forceHTML : isReply } ) ;
94
+ if ( formattedBody ) {
95
+ newContent . format = "org.matrix.custom.html" ;
96
+ newContent . formatted_body = htmlPrefix + formattedBody ;
97
+ contentBody . format = newContent . format ;
98
+ contentBody . formatted_body = `${ htmlPrefix } * ${ formattedBody } ` ;
99
+ }
100
+
101
+ return Object . assign ( {
102
+ "m.new_content" : newContent ,
103
+ "m.relates_to" : {
104
+ "rel_type" : "m.replace" ,
105
+ "event_id" : editedEvent . getId ( ) ,
106
+ } ,
107
+ } , contentBody ) ;
108
+ }
109
+
36
110
export default class MessageEditor extends React . Component {
37
111
static propTypes = {
38
112
// the message event being edited
@@ -53,7 +127,7 @@ export default class MessageEditor extends React.Component {
53
127
} ;
54
128
this . _editorRef = null ;
55
129
this . _autocompleteRef = null ;
56
- this . _hasModifications = false ;
130
+ this . _modifiedFlag = false ;
57
131
}
58
132
59
133
_getRoom ( ) {
@@ -73,7 +147,7 @@ export default class MessageEditor extends React.Component {
73
147
}
74
148
75
149
_onInput = ( event ) => {
76
- this . _hasModifications = true ;
150
+ this . _modifiedFlag = true ;
77
151
const sel = document . getSelection ( ) ;
78
152
const { caret, text} = getCaretOffsetAndText ( this . _editorRef , sel ) ;
79
153
this . model . update ( text , event . inputType , caret ) ;
@@ -131,7 +205,7 @@ export default class MessageEditor extends React.Component {
131
205
} else if ( event . key === "Escape" ) {
132
206
this . _cancelEdit ( ) ;
133
207
} else if ( event . key === "ArrowUp" ) {
134
- if ( this . _hasModifications || ! this . _isCaretAtStart ( ) ) {
208
+ if ( this . _modifiedFlag || ! this . _isCaretAtStart ( ) ) {
135
209
return ;
136
210
}
137
211
const previousEvent = findEditableEvent ( this . _getRoom ( ) , false , this . props . editState . getEvent ( ) . getId ( ) ) ;
@@ -140,7 +214,7 @@ export default class MessageEditor extends React.Component {
140
214
event . preventDefault ( ) ;
141
215
}
142
216
} else if ( event . key === "ArrowDown" ) {
143
- if ( this . _hasModifications || ! this . _isCaretAtEnd ( ) ) {
217
+ if ( this . _modifiedFlag || ! this . _isCaretAtEnd ( ) ) {
144
218
return ;
145
219
}
146
220
const nextEvent = findEditableEvent ( this . _getRoom ( ) , true , this . props . editState . getEvent ( ) . getId ( ) ) ;
@@ -159,56 +233,28 @@ export default class MessageEditor extends React.Component {
159
233
dis . dispatch ( { action : 'focus_composer' } ) ;
160
234
}
161
235
162
- _isEmote ( ) {
163
- const firstPart = this . model . parts [ 0 ] ;
164
- return firstPart && firstPart . type === "plain" && firstPart . text . startsWith ( "/me " ) ;
165
- }
166
-
167
- _sendEdit = ( ) => {
168
- const isEmote = this . _isEmote ( ) ;
169
- let model = this . model ;
170
- if ( isEmote ) {
171
- // trim "/me "
172
- model = model . clone ( ) ;
173
- model . removeText ( { index : 0 , offset : 0 } , 4 ) ;
174
- }
175
- const newContent = {
176
- "msgtype" : isEmote ? "m.emote" : "m.text" ,
177
- "body" : textSerialize ( model ) ,
178
- } ;
179
- const contentBody = {
180
- msgtype : newContent . msgtype ,
181
- body : ` * ${ newContent . body } ` ,
182
- } ;
183
- const formattedBody = htmlSerializeIfNeeded ( model ) ;
184
- if ( formattedBody ) {
185
- newContent . format = "org.matrix.custom.html" ;
186
- newContent . formatted_body = formattedBody ;
187
- contentBody . format = newContent . format ;
188
- contentBody . formatted_body = ` * ${ newContent . formatted_body } ` ;
189
- }
190
-
236
+ _hasModifications ( newContent ) {
191
237
// if nothing has changed then bail
192
238
const oldContent = this . props . editState . getEvent ( ) . getContent ( ) ;
193
- if ( ! this . _hasModifications ||
239
+ if ( ! this . _modifiedFlag ||
194
240
( oldContent [ "msgtype" ] === newContent [ "msgtype" ] && oldContent [ "body" ] === newContent [ "body" ] &&
195
241
oldContent [ "format" ] === newContent [ "format" ] &&
196
242
oldContent [ "formatted_body" ] === newContent [ "formatted_body" ] ) ) {
197
- this . _cancelEdit ( ) ;
198
- return ;
243
+ return false ;
199
244
}
245
+ return true ;
246
+ }
200
247
201
- const content = Object . assign ( {
202
- "m.new_content" : newContent ,
203
- "m.relates_to" : {
204
- "rel_type" : "m.replace" ,
205
- "event_id" : this . props . editState . getEvent ( ) . getId ( ) ,
206
- } ,
207
- } , contentBody ) ;
208
-
209
- const roomId = this . props . editState . getEvent ( ) . getRoomId ( ) ;
248
+ _sendEdit = ( ) => {
249
+ const editedEvent = this . props . editState . getEvent ( ) ;
250
+ const editContent = createEditContent ( this . model , editedEvent ) ;
251
+ const newContent = editContent [ "m.new_content" ] ;
252
+ if ( ! this . _hasModifications ( newContent ) ) {
253
+ return ;
254
+ }
255
+ const roomId = editedEvent . getRoomId ( ) ;
210
256
this . _cancelPreviousPendingEdit ( ) ;
211
- this . context . matrixClient . sendMessage ( roomId , content ) ;
257
+ this . context . matrixClient . sendMessage ( roomId , editContent ) ;
212
258
213
259
dis . dispatch ( { action : "edit_event" , event : null } ) ;
214
260
dis . dispatch ( { action : 'focus_composer' } ) ;
0 commit comments