@@ -361,6 +361,46 @@ interface IOpts {
361
361
ref ?: React . Ref < any > ;
362
362
}
363
363
364
+ /**
365
+ * Wraps emojis in <span> to style them separately from the rest of message. Consecutive emojis (and modifiers) are wrapped
366
+ * in the same <span>.
367
+ * @param {string } message the text to format
368
+ * @param {boolean } isHtmlMessage whether the message contains HTML
369
+ * @returns if isHtmlMessage is true, returns an array of strings, otherwise return an array of React Elements for emojis
370
+ * and plain text for everything else
371
+ */
372
+ function formatEmojis ( message : string , isHtmlMessage : boolean ) : ( JSX . Element | string ) [ ] {
373
+ const emojiToSpan = isHtmlMessage ? ( emoji : string ) => `<span class='mx_EventTile_Emoji'>${ emoji } </span>` :
374
+ ( emoji : string , key : number ) => < span key = { key } className = 'mx_EventTile_Emoji' > { emoji } </ span > ;
375
+ const result : ( JSX . Element | string ) [ ] = [ ] ;
376
+ let text = '' ;
377
+ let emojis = '' ;
378
+ let key = 0 ;
379
+ for ( const char of message ) {
380
+ if ( mightContainEmoji ( char ) ) {
381
+ if ( text ) {
382
+ result . push ( text ) ;
383
+ text = '' ;
384
+ }
385
+ emojis += char ;
386
+ } else {
387
+ if ( emojis ) {
388
+ result . push ( emojiToSpan ( emojis , key ) ) ;
389
+ key ++ ;
390
+ emojis = '' ;
391
+ }
392
+ text += char ;
393
+ }
394
+ }
395
+ if ( text ) {
396
+ result . push ( text ) ;
397
+ }
398
+ if ( emojis ) {
399
+ result . push ( emojiToSpan ( emojis , key ) ) ;
400
+ }
401
+ return result ;
402
+ }
403
+
364
404
/* turn a matrix event body into html
365
405
*
366
406
* content: 'content' of the MatrixEvent
@@ -414,6 +454,9 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
414
454
if ( isHtmlMessage ) {
415
455
isDisplayedWithHtml = true ;
416
456
safeBody = sanitizeHtml ( formattedBody , sanitizeParams ) ;
457
+ if ( bodyHasEmoji ) {
458
+ safeBody = formatEmojis ( safeBody , true ) . join ( '' ) ;
459
+ }
417
460
}
418
461
} finally {
419
462
delete sanitizeParams . textFilter ;
@@ -455,14 +498,21 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts
455
498
'markdown-body' : isHtmlMessage && ! emojiBody ,
456
499
} ) ;
457
500
501
+ let emojiBodyElements : JSX . Element [ ] ;
502
+ if ( ! isDisplayedWithHtml && bodyHasEmoji && ! emojiBody ) {
503
+ emojiBodyElements = formatEmojis ( strippedBody , false ) as JSX . Element [ ] ;
504
+ }
505
+
458
506
return isDisplayedWithHtml ?
459
507
< span
460
508
key = "body"
461
509
ref = { opts . ref }
462
510
className = { className }
463
511
dangerouslySetInnerHTML = { { __html : safeBody } }
464
512
dir = "auto"
465
- /> : < span key = "body" ref = { opts . ref } className = { className } dir = "auto" > { strippedBody } </ span > ;
513
+ /> : < span key = "body" ref = { opts . ref } className = { className } dir = "auto" >
514
+ { emojiBodyElements || strippedBody }
515
+ </ span > ;
466
516
}
467
517
468
518
/**
0 commit comments