@@ -46,6 +46,8 @@ import {containsEmoji} from "../../../effects/utils";
46
46
import { CHAT_EFFECTS } from '../../../effects' ;
47
47
import SettingsStore from "../../../settings/SettingsStore" ;
48
48
import CountlyAnalytics from "../../../CountlyAnalytics" ;
49
+ import { MatrixClientPeg } from "../../../MatrixClientPeg" ;
50
+ import EMOJI_REGEX from 'emojibase-regex' ;
49
51
50
52
function addReplyToMessageContent ( content , repliedToEvent , permalinkCreator ) {
51
53
const replyContent = ReplyThread . makeReplyMixIn ( repliedToEvent ) ;
@@ -91,6 +93,24 @@ export function createMessageContent(model, permalinkCreator, replyToEvent) {
91
93
return content ;
92
94
}
93
95
96
+ // exported for tests
97
+ export function isQuickReaction ( model ) {
98
+ const parts = model . parts ;
99
+ if ( parts . length == 0 ) return false ;
100
+ const text = textSerialize ( model ) ;
101
+ // shortcut takes the form "+:emoji:" or "+ :emoji:""
102
+ // can be in 1 or 2 parts
103
+ if ( parts . length <= 2 ) {
104
+ const hasShortcut = text . startsWith ( "+" ) || text . startsWith ( "+ " ) ;
105
+ const emojiMatch = text . match ( EMOJI_REGEX ) ;
106
+ if ( hasShortcut && emojiMatch && emojiMatch . length == 1 ) {
107
+ return emojiMatch [ 0 ] === text . substring ( 1 ) ||
108
+ emojiMatch [ 0 ] === text . substring ( 2 ) ;
109
+ }
110
+ }
111
+ return false ;
112
+ }
113
+
94
114
export default class SendMessageComposer extends React . Component {
95
115
static propTypes = {
96
116
room : PropTypes . object . isRequired ,
@@ -223,6 +243,41 @@ export default class SendMessageComposer extends React.Component {
223
243
return false ;
224
244
}
225
245
246
+ _sendQuickReaction ( ) {
247
+ const timeline = this . props . room . getLiveTimeline ( ) ;
248
+ const events = timeline . getEvents ( ) ;
249
+ const reaction = this . model . parts [ 1 ] . text ;
250
+ for ( let i = events . length - 1 ; i >= 0 ; i -- ) {
251
+ if ( events [ i ] . getType ( ) === "m.room.message" ) {
252
+ let shouldReact = true ;
253
+ const lastMessage = events [ i ] ;
254
+ const userId = MatrixClientPeg . get ( ) . getUserId ( ) ;
255
+ const messageReactions = this . props . room . getUnfilteredTimelineSet ( )
256
+ . getRelationsForEvent ( lastMessage . getId ( ) , "m.annotation" , "m.reaction" ) ;
257
+
258
+ // if we have already sent this reaction, don't redact but don't re-send
259
+ if ( messageReactions ) {
260
+ const myReactionEvents = messageReactions . getAnnotationsBySender ( ) [ userId ] || [ ] ;
261
+ const myReactionKeys = [ ...myReactionEvents ]
262
+ . filter ( event => ! event . isRedacted ( ) )
263
+ . map ( event => event . getRelation ( ) . key ) ;
264
+ shouldReact = ! myReactionKeys . includes ( reaction ) ;
265
+ }
266
+ if ( shouldReact ) {
267
+ MatrixClientPeg . get ( ) . sendEvent ( lastMessage . getRoomId ( ) , "m.reaction" , {
268
+ "m.relates_to" : {
269
+ "rel_type" : "m.annotation" ,
270
+ "event_id" : lastMessage . getId ( ) ,
271
+ "key" : reaction ,
272
+ } ,
273
+ } ) ;
274
+ dis . dispatch ( { action : "message_sent" } ) ;
275
+ }
276
+ break ;
277
+ }
278
+ }
279
+ }
280
+
226
281
_getSlashCommand ( ) {
227
282
const commandText = this . model . parts . reduce ( ( text , part ) => {
228
283
// use mxid to textify user pills in a command
@@ -310,6 +365,11 @@ export default class SendMessageComposer extends React.Component {
310
365
}
311
366
}
312
367
368
+ if ( isQuickReaction ( this . model ) ) {
369
+ shouldSend = false ;
370
+ this . _sendQuickReaction ( ) ;
371
+ }
372
+
313
373
const replyToEvent = this . props . replyToEvent ;
314
374
if ( shouldSend ) {
315
375
const startTime = CountlyAnalytics . getTimestamp ( ) ;
0 commit comments