chore: initial commit
This commit is contained in:
22
components/Emoji/Emoji.tsx
Normal file
22
components/Emoji/Emoji.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { Emoji as EmojiMart } from 'emoji-mart'
|
||||
|
||||
import { emojiSet } from './emojiPlugin'
|
||||
|
||||
export interface EmojiProps {
|
||||
value: string
|
||||
size: number
|
||||
}
|
||||
|
||||
export const Emoji: React.FC<EmojiProps> = (props) => {
|
||||
const { value, size } = props
|
||||
|
||||
return (
|
||||
<EmojiMart
|
||||
set={emojiSet}
|
||||
emoji={value}
|
||||
size={size}
|
||||
tooltip
|
||||
fallback={() => <>{value}</>}
|
||||
/>
|
||||
)
|
||||
}
|
28
components/Emoji/EmojiPicker.tsx
Normal file
28
components/Emoji/EmojiPicker.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import 'emoji-mart/css/emoji-mart.css'
|
||||
import { EmojiData, Picker } from 'emoji-mart'
|
||||
|
||||
import { useTheme } from 'contexts/Theme'
|
||||
import { emojiSet } from './emojiPlugin'
|
||||
|
||||
export type EmojiPickerOnClick = (
|
||||
emoji: EmojiData,
|
||||
event: React.MouseEvent<HTMLElement, MouseEvent>
|
||||
) => void
|
||||
|
||||
export interface EmojiPickerProps {
|
||||
onClick: EmojiPickerOnClick
|
||||
}
|
||||
|
||||
export const EmojiPicker: React.FC<EmojiPickerProps> = (props) => {
|
||||
const { theme } = useTheme()
|
||||
|
||||
return (
|
||||
<Picker
|
||||
set={emojiSet}
|
||||
theme={theme}
|
||||
onClick={props.onClick}
|
||||
showPreview={false}
|
||||
showSkinTones={false}
|
||||
/>
|
||||
)
|
||||
}
|
63
components/Emoji/emojiPlugin.tsx
Normal file
63
components/Emoji/emojiPlugin.tsx
Normal file
@ -0,0 +1,63 @@
|
||||
import visit from 'unist-util-visit'
|
||||
import { Plugin, Transformer } from 'unified'
|
||||
import { Node } from 'unist'
|
||||
import { EmojiSet } from 'emoji-mart'
|
||||
|
||||
export const emojiSet: EmojiSet = 'twitter'
|
||||
|
||||
export const emojiRegex = /:\+1:|:-1:|:[\w-]+:/
|
||||
|
||||
export const isStringWithOnlyOneEmoji = (value: string): boolean => {
|
||||
const result = emojiRegex.exec(value)
|
||||
return result != null && result.input === result[0]
|
||||
}
|
||||
|
||||
const extractText = (string: string, start: number, end: number): Node => {
|
||||
const startLine = string.slice(0, start).split('\n')
|
||||
const endLine = string.slice(0, end).split('\n')
|
||||
return {
|
||||
type: 'text',
|
||||
value: string.slice(start, end),
|
||||
position: {
|
||||
start: {
|
||||
line: startLine.length,
|
||||
column: startLine[startLine.length - 1].length + 1
|
||||
},
|
||||
end: {
|
||||
line: endLine.length,
|
||||
column: endLine[endLine.length - 1].length + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const emojiPlugin: Plugin = () => {
|
||||
const transformer: Transformer = (tree) => {
|
||||
visit(tree, 'text', (node, position, parent) => {
|
||||
if (typeof node.value !== 'string') {
|
||||
return
|
||||
}
|
||||
const definition: Node[] = []
|
||||
let lastIndex = 0
|
||||
const match = emojiRegex.exec(node.value)
|
||||
if (match != null) {
|
||||
const value = match[0]
|
||||
if (match.index !== lastIndex) {
|
||||
definition.push(extractText(node.value, lastIndex, match.index))
|
||||
}
|
||||
definition.push({ type: 'emoji', value })
|
||||
lastIndex = match.index + value.length
|
||||
if (lastIndex !== node.value.length) {
|
||||
definition.push(extractText(node.value, lastIndex, node.value.length))
|
||||
}
|
||||
if (parent != null) {
|
||||
const last = parent.children.slice(position + 1)
|
||||
parent.children = parent.children.slice(0, position)
|
||||
parent.children = parent.children.concat(definition)
|
||||
parent.children = parent.children.concat(last)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return transformer
|
||||
}
|
3
components/Emoji/index.ts
Normal file
3
components/Emoji/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './Emoji'
|
||||
export * from './EmojiPicker'
|
||||
export * from './emojiPlugin'
|
Reference in New Issue
Block a user