feat(pages): add /application/[guildId]/[channelId]
(#4)
This commit is contained in:
15
components/Emoji/Emoji.stories.tsx
Normal file
15
components/Emoji/Emoji.stories.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { Meta, Story } from '@storybook/react'
|
||||
|
||||
import { Emoji as Component, EmojiProps } from './Emoji'
|
||||
|
||||
const Stories: Meta = {
|
||||
title: 'Emoji',
|
||||
component: Component
|
||||
}
|
||||
|
||||
export default Stories
|
||||
|
||||
export const Emoji: Story<EmojiProps> = (arguments_) => {
|
||||
return <Component {...arguments_} />
|
||||
}
|
||||
Emoji.args = { value: ':wave:', size: 20 }
|
24
components/Emoji/Emoji.tsx
Normal file
24
components/Emoji/Emoji.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { Emoji as EmojiMart } from 'emoji-mart'
|
||||
|
||||
import { EMOJI_SET } from './emojiPlugin'
|
||||
|
||||
export interface EmojiProps {
|
||||
value: string
|
||||
size: number
|
||||
}
|
||||
|
||||
export const Emoji: React.FC<EmojiProps> = (props) => {
|
||||
const { value, size } = props
|
||||
|
||||
return (
|
||||
<EmojiMart
|
||||
set={EMOJI_SET}
|
||||
emoji={value}
|
||||
size={size}
|
||||
tooltip
|
||||
fallback={() => {
|
||||
return <>{value}</>
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
15
components/Emoji/EmojiPicker/EmojiPicker.stories.tsx
Normal file
15
components/Emoji/EmojiPicker/EmojiPicker.stories.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { Meta, Story } from '@storybook/react'
|
||||
|
||||
import { EmojiPicker as Component, EmojiPickerProps } from './EmojiPicker'
|
||||
|
||||
const Stories: Meta = {
|
||||
title: 'EmojiPicker',
|
||||
component: Component
|
||||
}
|
||||
|
||||
export default Stories
|
||||
|
||||
export const EmojiPicker: Story<EmojiPickerProps> = (arguments_) => {
|
||||
return <Component {...arguments_} />
|
||||
}
|
||||
EmojiPicker.args = { onClick: (emoji, event) => console.log(emoji, event) }
|
28
components/Emoji/EmojiPicker/EmojiPicker.tsx
Normal file
28
components/Emoji/EmojiPicker/EmojiPicker.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import 'emoji-mart/css/emoji-mart.css'
|
||||
import { EmojiData, Picker } from 'emoji-mart'
|
||||
import { useTheme } from 'next-themes'
|
||||
|
||||
import { EMOJI_SET } 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={EMOJI_SET}
|
||||
theme={theme as 'light' | 'dark' | 'auto'}
|
||||
onClick={props.onClick}
|
||||
showPreview={false}
|
||||
showSkinTones={false}
|
||||
/>
|
||||
)
|
||||
}
|
1
components/Emoji/EmojiPicker/index.ts
Normal file
1
components/Emoji/EmojiPicker/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './EmojiPicker'
|
71
components/Emoji/emojiPlugin.ts
Normal file
71
components/Emoji/emojiPlugin.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { visit } from 'unist-util-visit'
|
||||
import { Plugin, Transformer } from 'unified'
|
||||
import { Literal, Parent } from 'unist'
|
||||
import type { ElementContent } from 'hast'
|
||||
import type { EmojiSet } from 'emoji-mart'
|
||||
|
||||
import { emojiRegex } from './isStringWithOnlyOneEmoji'
|
||||
|
||||
export const EMOJI_SET: EmojiSet = 'twitter'
|
||||
|
||||
const extractText = (
|
||||
string: string,
|
||||
start: number,
|
||||
end: number
|
||||
): ElementContent => {
|
||||
return {
|
||||
type: 'text',
|
||||
value: string.slice(start, end)
|
||||
}
|
||||
}
|
||||
|
||||
export const emojiPlugin: Plugin<[], Literal<string>> = () => {
|
||||
const transformer: Transformer<Literal<string>> = (tree) => {
|
||||
visit<Literal<string>, string>(
|
||||
tree,
|
||||
'text',
|
||||
(node, position, parent: Parent<ElementContent> | null) => {
|
||||
if (typeof node.value !== 'string') {
|
||||
return
|
||||
}
|
||||
position = position ?? 0
|
||||
const definition: ElementContent[] = []
|
||||
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: 'element',
|
||||
tagName: 'emoji',
|
||||
properties: { value },
|
||||
children: []
|
||||
})
|
||||
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
|
||||
}
|
||||
|
||||
declare global {
|
||||
namespace JSX {
|
||||
interface IntrinsicElements {
|
||||
emoji: { value: string }
|
||||
}
|
||||
}
|
||||
}
|
4
components/Emoji/index.ts
Normal file
4
components/Emoji/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './Emoji'
|
||||
export * from './EmojiPicker'
|
||||
export * from './emojiPlugin'
|
||||
export * from './isStringWithOnlyOneEmoji'
|
18
components/Emoji/isStringWithOnlyOneEmoji.test.ts
Normal file
18
components/Emoji/isStringWithOnlyOneEmoji.test.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { isStringWithOnlyOneEmoji } from './isStringWithOnlyOneEmoji'
|
||||
|
||||
describe('components/Emoji/isStringWithOnlyOneEmoji', () => {
|
||||
it('returns true with a string with only one emoji', () => {
|
||||
expect(isStringWithOnlyOneEmoji(':wave:')).toBeTruthy()
|
||||
expect(isStringWithOnlyOneEmoji(':smile:')).toBeTruthy()
|
||||
})
|
||||
|
||||
it('returns false with a string with multiple emoji or with text', () => {
|
||||
expect(isStringWithOnlyOneEmoji(':wave: :smile:')).toBeFalsy()
|
||||
expect(isStringWithOnlyOneEmoji(':wave: some text')).toBeFalsy()
|
||||
expect(isStringWithOnlyOneEmoji('some text :wave:')).toBeFalsy()
|
||||
})
|
||||
|
||||
it('returns false with a string without emoji', () => {
|
||||
expect(isStringWithOnlyOneEmoji('some text')).toBeFalsy()
|
||||
})
|
||||
})
|
6
components/Emoji/isStringWithOnlyOneEmoji.ts
Normal file
6
components/Emoji/isStringWithOnlyOneEmoji.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export const emojiRegex = /:\+1:|:-1:|:[\w-]+:/
|
||||
|
||||
export const isStringWithOnlyOneEmoji = (value: string): boolean => {
|
||||
const result = emojiRegex.exec(value)
|
||||
return result != null && result.input === result[0]
|
||||
}
|
Reference in New Issue
Block a user