mirror of
https://github.com/owncast/owncast.git
synced 2024-11-28 11:09:01 +03:00
emoji: display native emoji simiarly to custom (#3147)
This commit is contained in:
parent
cc75be1c00
commit
58bc3ac173
5 changed files with 79 additions and 0 deletions
|
@ -6,6 +6,7 @@ import dynamic from 'next/dynamic';
|
||||||
import { Interweave } from 'interweave';
|
import { Interweave } from 'interweave';
|
||||||
import { UrlMatcher } from 'interweave-autolink';
|
import { UrlMatcher } from 'interweave-autolink';
|
||||||
import { ChatMessageHighlightMatcher } from './customMatcher';
|
import { ChatMessageHighlightMatcher } from './customMatcher';
|
||||||
|
import { ChatMessageEmojiMatcher } from './emojiMatcher';
|
||||||
import styles from './ChatUserMessage.module.scss';
|
import styles from './ChatUserMessage.module.scss';
|
||||||
import { formatTimestamp } from './messageFmt';
|
import { formatTimestamp } from './messageFmt';
|
||||||
import { ChatMessage } from '../../../interfaces/chat-message.model';
|
import { ChatMessage } from '../../../interfaces/chat-message.model';
|
||||||
|
@ -111,6 +112,7 @@ export const ChatUserMessage: FC<ChatUserMessageProps> = ({
|
||||||
matchers={[
|
matchers={[
|
||||||
new UrlMatcher('url', { customTLDs: ['online'] }),
|
new UrlMatcher('url', { customTLDs: ['online'] }),
|
||||||
new ChatMessageHighlightMatcher('highlight', { highlightString }),
|
new ChatMessageHighlightMatcher('highlight', { highlightString }),
|
||||||
|
new ChatMessageEmojiMatcher('emoji', { className: 'emoji' }),
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
71
web/components/chat/ChatUserMessage/emojiMatcher.ts
Normal file
71
web/components/chat/ChatUserMessage/emojiMatcher.ts
Normal file
|
@ -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';
|
||||||
|
}
|
||||||
|
}
|
1
web/package-lock.json
generated
1
web/package-lock.json
generated
|
@ -44,6 +44,7 @@
|
||||||
"react-markdown": "8.0.7",
|
"react-markdown": "8.0.7",
|
||||||
"react-virtuoso": "4.3.11",
|
"react-virtuoso": "4.3.11",
|
||||||
"recoil": "0.7.7",
|
"recoil": "0.7.7",
|
||||||
|
"regexpu-core": "^5.3.2",
|
||||||
"sanitize-html": "^2.11.0",
|
"sanitize-html": "^2.11.0",
|
||||||
"sharp": "0.32.1",
|
"sharp": "0.32.1",
|
||||||
"ua-parser-js": "1.0.35",
|
"ua-parser-js": "1.0.35",
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
"react-markdown": "8.0.7",
|
"react-markdown": "8.0.7",
|
||||||
"react-virtuoso": "4.3.11",
|
"react-virtuoso": "4.3.11",
|
||||||
"recoil": "0.7.7",
|
"recoil": "0.7.7",
|
||||||
|
"regexpu-core": "^5.3.2",
|
||||||
"sanitize-html": "^2.11.0",
|
"sanitize-html": "^2.11.0",
|
||||||
"sharp": "0.32.1",
|
"sharp": "0.32.1",
|
||||||
"ua-parser-js": "1.0.35",
|
"ua-parser-js": "1.0.35",
|
||||||
|
|
|
@ -125,9 +125,13 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
.emoji {
|
.emoji {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
body::-webkit-scrollbar {
|
body::-webkit-scrollbar {
|
||||||
|
|
Loading…
Reference in a new issue