@@ -395,6 +395,46 @@ export interface IOptsReturnString extends IOpts {
395
395
returnString : true ;
396
396
}
397
397
398
+ /**
399
+ * Wraps emojis in <span> to style them separately from the rest of message. Consecutive emojis (and modifiers) are wrapped
400
+ * in the same <span>.
401
+ * @param {string } message the text to format
402
+ * @param {boolean } isHtmlMessage whether the message contains HTML
403
+ * @returns if isHtmlMessage is true, returns an array of strings, otherwise return an array of React Elements for emojis
404
+ * and plain text for everything else
405
+ */
406
+ function formatEmojis ( message : string , isHtmlMessage : boolean ) : ( JSX . Element | string ) [ ] {
407
+ const emojiToSpan = isHtmlMessage ? ( emoji : string ) => `<span class='mx_EventTile_Emoji'>${ emoji } </span>` :
408
+ ( emoji : string , key : number ) => < span key = { key } className = 'mx_EventTile_Emoji' > { emoji } </ span > ;
409
+ const result : ( JSX . Element | string ) [ ] = [ ] ;
410
+ let text = '' ;
411
+ let emojis = '' ;
412
+ let key = 0 ;
413
+ for ( const char of message ) {
414
+ if ( mightContainEmoji ( char ) || ZWJ_REGEX . test ( char ) || char === '\ufe0f' ) {
415
+ if ( text ) {
416
+ result . push ( text ) ;
417
+ text = '' ;
418
+ }
419
+ emojis += char ;
420
+ } else {
421
+ if ( emojis ) {
422
+ result . push ( emojiToSpan ( emojis , key ) ) ;
423
+ key ++ ;
424
+ emojis = '' ;
425
+ }
426
+ text += char ;
427
+ }
428
+ }
429
+ if ( text ) {
430
+ result . push ( text ) ;
431
+ }
432
+ if ( emojis ) {
433
+ result . push ( emojiToSpan ( emojis , key ) ) ;
434
+ }
435
+ return result ;
436
+ }
437
+
398
438
/* turn a matrix event body into html
399
439
*
400
440
* content: 'content' of the MatrixEvent
@@ -477,6 +517,9 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
477
517
} ) ;
478
518
safeBody = phtml . html ( ) ;
479
519
}
520
+ if ( bodyHasEmoji ) {
521
+ safeBody = formatEmojis ( safeBody , true ) . join ( '' ) ;
522
+ }
480
523
}
481
524
} finally {
482
525
delete sanitizeParams . textFilter ;
@@ -519,14 +562,21 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
519
562
'markdown-body' : isHtmlMessage && ! emojiBody ,
520
563
} ) ;
521
564
565
+ let emojiBodyElements : JSX . Element [ ] ;
566
+ if ( ! isDisplayedWithHtml && bodyHasEmoji && ! emojiBody ) {
567
+ emojiBodyElements = formatEmojis ( strippedBody , false ) as JSX . Element [ ] ;
568
+ }
569
+
522
570
return isDisplayedWithHtml ?
523
571
< span
524
572
key = "body"
525
573
ref = { opts . ref }
526
574
className = { className }
527
575
dangerouslySetInnerHTML = { { __html : safeBody } }
528
576
dir = "auto"
529
- /> : < span key = "body" ref = { opts . ref } className = { className } dir = "auto" > { strippedBody } </ span > ;
577
+ /> : < span key = "body" ref = { opts . ref } className = { className } dir = "auto" >
578
+ { emojiBodyElements || strippedBody }
579
+ </ span > ;
530
580
}
531
581
532
582
/**
0 commit comments