@@ -365,6 +365,46 @@ interface IOpts {
365
365
ref ?: React . Ref < any > ;
366
366
}
367
367
368
+ /**
369
+ * Wraps emojis in <span> to style them separately from the rest of message. Consecutive emojis (and modifiers) are wrapped
370
+ * in the same <span>.
371
+ * @param {string } message the text to format
372
+ * @param {boolean } isHtmlMessage whether the message contains HTML
373
+ * @returns if isHtmlMessage is true, returns an array of strings, otherwise return an array of React Elements for emojis
374
+ * and plain text for everything else
375
+ */
376
+ function formatEmojis ( message : string , isHtmlMessage : boolean ) : ( JSX . Element | string ) [ ] {
377
+ const emojiToSpan = isHtmlMessage ? ( emoji : string ) => `<span class='mx_EventTile_Emoji'>${ emoji } </span>` :
378
+ ( emoji : string , key : number ) => < span key = { key } className = 'mx_EventTile_Emoji' > { emoji } </ span > ;
379
+ const result : ( JSX . Element | string ) [ ] = [ ] ;
380
+ let text = '' ;
381
+ let emojis = '' ;
382
+ let key = 0 ;
383
+ for ( const char of message ) {
384
+ if ( mightContainEmoji ( char ) || ZWJ_REGEX . test ( char ) || char === '\ufe0f' ) {
385
+ if ( text ) {
386
+ result . push ( text ) ;
387
+ text = '' ;
388
+ }
389
+ emojis += char ;
390
+ } else {
391
+ if ( emojis ) {
392
+ result . push ( emojiToSpan ( emojis , key ) ) ;
393
+ key ++ ;
394
+ emojis = '' ;
395
+ }
396
+ text += char ;
397
+ }
398
+ }
399
+ if ( text ) {
400
+ result . push ( text ) ;
401
+ }
402
+ if ( emojis ) {
403
+ result . push ( emojiToSpan ( emojis , key ) ) ;
404
+ }
405
+ return result ;
406
+ }
407
+
368
408
/* turn a matrix event body into html
369
409
*
370
410
* content: 'content' of the MatrixEvent
@@ -433,6 +473,9 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
433
473
} ) ;
434
474
safeBody = phtml . html ( ) ;
435
475
}
476
+ if ( bodyHasEmoji ) {
477
+ safeBody = formatEmojis ( safeBody , true ) . join ( '' ) ;
478
+ }
436
479
}
437
480
} finally {
438
481
delete sanitizeParams . textFilter ;
@@ -474,14 +517,21 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
474
517
'markdown-body' : isHtmlMessage && ! emojiBody ,
475
518
} ) ;
476
519
520
+ let emojiBodyElements : JSX . Element [ ] ;
521
+ if ( ! isDisplayedWithHtml && bodyHasEmoji && ! emojiBody ) {
522
+ emojiBodyElements = formatEmojis ( strippedBody , false ) as JSX . Element [ ] ;
523
+ }
524
+
477
525
return isDisplayedWithHtml ?
478
526
< span
479
527
key = "body"
480
528
ref = { opts . ref }
481
529
className = { className }
482
530
dangerouslySetInnerHTML = { { __html : safeBody } }
483
531
dir = "auto"
484
- /> : < span key = "body" ref = { opts . ref } className = { className } dir = "auto" > { strippedBody } </ span > ;
532
+ /> : < span key = "body" ref = { opts . ref } className = { className } dir = "auto" >
533
+ { emojiBodyElements || strippedBody }
534
+ </ span > ;
485
535
}
486
536
487
537
/**
0 commit comments