diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 429ac7ed4b6d..d3c683f30462 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -137,6 +137,11 @@ $left-gutter: 64px; margin-right: 10px; } +.mx_EventTile_Emoji { + font-size: 2rem; + vertical-align: bottom; +} + /* HACK to override line-height which is already marked important elsewhere */ .mx_EventTile_bigEmoji.mx_EventTile_bigEmoji { font-size: 48px !important; diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 2301ad250b48..1847386eb5f3 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -365,6 +365,46 @@ interface IOpts { ref?: React.Ref; } +/** + * 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 @@ -433,6 +473,9 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts }); safeBody = phtml.html(); } + if (bodyHasEmoji) { + safeBody = formatEmojis(safeBody, true).join(''); + } } } finally { delete sanitizeParams.textFilter; @@ -474,6 +517,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} + ; } /**