diff --git a/web/components/chat/ChatUserMessage/ChatUserMessage.tsx b/web/components/chat/ChatUserMessage/ChatUserMessage.tsx index 7d9bfee06..26b0cfe25 100644 --- a/web/components/chat/ChatUserMessage/ChatUserMessage.tsx +++ b/web/components/chat/ChatUserMessage/ChatUserMessage.tsx @@ -6,6 +6,7 @@ import dynamic from 'next/dynamic'; import { Interweave } from 'interweave'; import { UrlMatcher } from 'interweave-autolink'; import { ChatMessageHighlightMatcher } from './customMatcher'; +import { ChatMessageEmojiMatcher } from './emojiMatcher'; import styles from './ChatUserMessage.module.scss'; import { formatTimestamp } from './messageFmt'; import { ChatMessage } from '../../../interfaces/chat-message.model'; @@ -111,6 +112,7 @@ export const ChatUserMessage: FC = ({ matchers={[ new UrlMatcher('url', { customTLDs: ['online'] }), new ChatMessageHighlightMatcher('highlight', { highlightString }), + new ChatMessageEmojiMatcher('emoji', { className: 'emoji' }), ]} /> diff --git a/web/components/chat/ChatUserMessage/emojiMatcher.ts b/web/components/chat/ChatUserMessage/emojiMatcher.ts new file mode 100644 index 000000000..d3b354221 --- /dev/null +++ b/web/components/chat/ChatUserMessage/emojiMatcher.ts @@ -0,0 +1,71 @@ +/* eslint-disable class-methods-use-this */ +import { ChildrenNode, Matcher, MatchResponse, Node } from 'interweave'; +import rewritePattern from 'regexpu-core'; +import React from 'react'; + +export interface ChatMessageEmojiProps { + key: string; +} + +interface options { + className: string; +} + +const emojiPattern = '\\p{RGI_Emoji}+'; + +const regexSupportsUnicodeSets = (() => { + // Using a variable for regexpFlags to avoid eslint error about the flag + // being invalid. It's not invalid, it's just new. + const regexpFlags = 'v'; + + // A bit more working around eslint - just calling new RegExp throws an + // error about not saving the value / just using side effects. + let regexp = null; + try { + regexp = new RegExp(emojiPattern, regexpFlags); + } catch (_) { + return false; + } + + // We have to use the variable somehow. Since we didn't catch an error + // this line always returns true. + return regexp !== null; +})(); + +const emojiRegex = (() => { + const rewriteFlags = { + unicodeSetsFlag: regexSupportsUnicodeSets ? 'parse' : 'transform', + }; + const regexFlag = regexSupportsUnicodeSets ? 'v' : 'u'; + + const regexPattern = rewritePattern(emojiPattern, 'v', rewriteFlags); + + return new RegExp(regexPattern, regexFlag); +})(); + +export class ChatMessageEmojiMatcher extends Matcher { + match(str: string): MatchResponse<{}> | null { + const result = str.match(emojiRegex); + + if (!result) { + return null; + } + + return { + index: result.index!, + length: result[0].length, + match: result[0], + valid: true, + }; + } + + replaceWith(children: ChildrenNode, props: ChatMessageEmojiProps): Node { + const { key } = props; + const { className } = this.options as options; + return React.createElement('span', { key, className }, children); + } + + asTag(): string { + return 'span'; + } +} diff --git a/web/package-lock.json b/web/package-lock.json index 96b24736b..3dc9e2ee3 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -44,6 +44,7 @@ "react-markdown": "8.0.7", "react-virtuoso": "4.3.11", "recoil": "0.7.7", + "regexpu-core": "^5.3.2", "sanitize-html": "^2.11.0", "sharp": "0.32.1", "ua-parser-js": "1.0.35", diff --git a/web/package.json b/web/package.json index 2a191bebb..2c1c55389 100644 --- a/web/package.json +++ b/web/package.json @@ -49,6 +49,7 @@ "react-markdown": "8.0.7", "react-virtuoso": "4.3.11", "recoil": "0.7.7", + "regexpu-core": "^5.3.2", "sanitize-html": "^2.11.0", "sharp": "0.32.1", "ua-parser-js": "1.0.35", diff --git a/web/styles/globals.scss b/web/styles/globals.scss index 6690befe4..070bef4e6 100644 --- a/web/styles/globals.scss +++ b/web/styles/globals.scss @@ -125,9 +125,13 @@ body { } .emoji { + display: inline-block; + font-size: 30px; height: 30px; + line-height: 30px; margin-left: 5px; margin-right: 5px; + vertical-align: middle; } body::-webkit-scrollbar {