diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index a58ba3064d..b077dc8bbc 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -233,6 +233,11 @@ $left-gutter: 64px; overflow-y: hidden; } + .mx_EventTile_Emoji { + font-size: 1.8rem; + vertical-align: bottom; + } + &.mx_EventTile_selected .mx_EventTile_line, &:hover .mx_EventTile_line { border-top-left-radius: 4px; diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index b526d48edd..e50d868ef9 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -402,6 +402,46 @@ export interface IOptsReturnString extends IOpts { returnString: true; } +/** + * Wraps emojis in to style them separately from the rest of message. Consecutive emojis (and modifiers) are wrapped + * in the same . + * @param {string} message the text to format + * @param {boolean} isHtmlMessage whether the message contains HTML + * @returns if isHtmlMessage is true, returns an array of strings, otherwise return an array of React Elements for emojis + * and plain text for everything else + */ +function formatEmojis(message: string, isHtmlMessage: boolean): (JSX.Element | string)[] { + const emojiToSpan = isHtmlMessage ? (emoji: string) => `${emoji}` : + (emoji: string, key: number) => { emoji }; + const result: (JSX.Element | string)[] = []; + let text = ''; + let emojis = ''; + let key = 0; + for (const char of message) { + if (mightContainEmoji(char) || ZWJ_REGEX.test(char) || char === '\ufe0f') { + if (text) { + result.push(text); + text = ''; + } + emojis += char; + } else { + if (emojis) { + result.push(emojiToSpan(emojis, key)); + key++; + emojis = ''; + } + text += char; + } + } + if (text) { + result.push(text); + } + if (emojis) { + result.push(emojiToSpan(emojis, key)); + } + return result; +} + /* turn a matrix event body into html * * content: 'content' of the MatrixEvent @@ -488,6 +528,9 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts }); safeBody = phtml.html(); } + if (bodyHasEmoji) { + safeBody = formatEmojis(safeBody, true).join(''); + } } } finally { delete sanitizeParams.textFilter; @@ -530,6 +573,11 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts 'markdown-body': isHtmlMessage && !emojiBody, }); + let emojiBodyElements: JSX.Element[]; + if (!isDisplayedWithHtml && bodyHasEmoji && !emojiBody) { + emojiBodyElements = formatEmojis(strippedBody, false) as JSX.Element[]; + } + return isDisplayedWithHtml ? : { strippedBody }; + /> : + { emojiBodyElements || strippedBody } + ; } /**