Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rich-text-editor): make emoji plugin a component #3708

Merged
merged 6 commits into from
Jul 14, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
LinkPlugin,
} from '../plugins'
import ToolbarPlugin from '../LexicalEditorToolbarPlugin'
import type { CustomEmojiGroup } from './types'

jest.mock('../LexicalEditorToolbarPlugin', () => ({
__esModule: true,
Expand Down Expand Up @@ -156,8 +155,6 @@ describe('LexicalEditor', () => {
expect(mockedToolbarPlugin).toHaveBeenCalledWith(
{
disabled: true,
customEmojis: undefined,
plugins: [],
toolbarRef: {
current: null,
},
Expand All @@ -174,8 +171,6 @@ describe('LexicalEditor', () => {
expect(mockedToolbarPlugin).toHaveBeenCalledWith(
{
disabled: true,
customEmojis: undefined,
plugins: [],
toolbarRef: {
current: null,
},
Expand All @@ -189,15 +184,13 @@ describe('LexicalEditor', () => {
it('renders ToolbarPlugin with correct props', () => {
renderLexicalEditor({
disabled: true,
customEmojis: ['foo' as unknown as CustomEmojiGroup],
customEmojis: ['foo' as any],
plugins: ['link'],
})

expect(mockedToolbarPlugin).toHaveBeenCalledWith(
{
disabled: true,
customEmojis: ['foo'],
plugins: ['link'],
toolbarRef: {
current: null,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ import { $getRoot } from 'lexical'

import ToolbarPlugin from '../LexicalEditorToolbarPlugin'
import { RTEPluginContextProvider } from '../plugins/api'
import { CustomEmojiNode } from '../plugins/EmojiPlugin/nodes/CustomEmojiNode'
import {
EmojiPlugin,
ListPlugin,
TextLengthPlugin,
HeadingsReplacementPlugin,
Expand All @@ -35,11 +33,11 @@ import { useComponentPlugins } from './hooks/useComponentPlugins/useComponentPlu
import styles from './styles'
import type {
ChangeHandler,
CustomEmojiGroup,
EditorPlugin,
TextLengthChangeHandler,
} from './types'
import { cleanupHtmlOutput, createLexicalTheme, setEditorValue } from './utils'
import type { CustomEmojiGroup } from '../plugins/EmojiPlugin'

const useStyles = makeStyles<Theme>(styles, {
name: 'LexicalEditor',
Expand Down Expand Up @@ -96,7 +94,7 @@ export type Props = BaseProps & {
}

const LexicalEditor = forwardRef<HTMLDivElement, Props>(function LexicalEditor(
props,
props: Props,
ref
) {
const {
Expand Down Expand Up @@ -133,7 +131,10 @@ const LexicalEditor = forwardRef<HTMLDivElement, Props>(function LexicalEditor(
[typographyClassNames, classes]
)

const { componentPlugins, lexicalNodes } = useComponentPlugins(plugins)
const { componentPlugins, lexicalNodes } = useComponentPlugins(
plugins,
customEmojis
augustobmoura marked this conversation as resolved.
Show resolved Hide resolved
)

const editorConfig: InitialConfigType = useMemo(
() => ({
Expand All @@ -144,13 +145,7 @@ const LexicalEditor = forwardRef<HTMLDivElement, Props>(function LexicalEditor(
throw error
},
namespace: 'editor',
nodes: [
CustomEmojiNode,
ListNode,
ListItemNode,
HeadingNode,
...lexicalNodes,
],
nodes: [ListNode, ListItemNode, HeadingNode, ...lexicalNodes],
editable: !disabled,
}),
[defaultValue, theme, disabled, lexicalNodes]
Expand Down Expand Up @@ -198,8 +193,6 @@ const LexicalEditor = forwardRef<HTMLDivElement, Props>(function LexicalEditor(
toolbarRef={toolbarRef}
// remount Toolbar when disabled
key={`${disabled || !focused}`}
customEmojis={customEmojis}
plugins={plugins}
testIds={testIds}
/>

Expand All @@ -213,7 +206,6 @@ const LexicalEditor = forwardRef<HTMLDivElement, Props>(function LexicalEditor(
<HeadingsReplacementPlugin />
<TextLengthPlugin onTextLengthChange={onTextLengthChange} />
<ListPlugin />
<EmojiPlugin />
<HistoryPlugin />
{hiddenInputId && (
<FocusOnLabelClickPlugin hiddenInputId={hiddenInputId} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,45 @@
import type { ReactElement } from 'react'
import React, { cloneElement } from 'react'

import { isRTEPluginElement, RTEPluginMeta } from '../../../plugins/api'
import { LinkPlugin } from '../../../plugins'
import type { EditorPlugin } from '../..'
import { LinkPlugin } from '../../../plugins'
import type { RTEPlugin } from '../../../plugins/api'
import { isRTEPluginElement, RTEPluginMeta } from '../../../plugins/api'
import type { CustomEmojiGroup } from '../../../plugins/EmojiPlugin'
import EmojiPlugin from '../../../plugins/EmojiPlugin'

export const useComponentPlugins = (plugins: EditorPlugin[]) => {
const mappedPlugins: EditorPlugin[] = plugins.map(plugin => {
switch (plugin) {
case 'link':
return <LinkPlugin />
const uniquePlugins = () => {
const plugins = new Set()

default:
return plugin
return ({ type }: ReactElement<unknown, RTEPlugin<unknown>>): boolean => {
if (plugins.has(type)) {
return false
}
})

plugins.add(type)

return true
}
}

export const useComponentPlugins = (
plugins: EditorPlugin[],
customEmojis: CustomEmojiGroup[] | undefined
) => {
const mappedPlugins: EditorPlugin[] = plugins
.map(plugin => {
switch (plugin) {
case 'link':
return <LinkPlugin />

case 'emoji':
return <EmojiPlugin customEmojis={customEmojis} />

default:
return plugin
}
})
.filter(uniquePlugins())

const componentPlugins = mappedPlugins.filter(isRTEPluginElement)

Expand Down
28 changes: 0 additions & 28 deletions packages/picasso-rich-text-editor/src/LexicalEditor/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,3 @@ export type EditorPlugin =
| 'link'
| 'emoji'
| ReactElement<unknown, RTEPlugin<unknown>>

export type CustomEmoji = {
id: string
name: string
keywords: string[]
skins: [
{
src: string
}
]
}

export type CustomEmojiGroup = {
id: string
name: string
emojis: CustomEmoji[]
}

export type Emoji = {
id: string
name: string
native?: string
unified?: string
keywords: string[]
shortcodes: string
emoticons?: string[]
src?: string
}
Original file line number Diff line number Diff line change
@@ -1,44 +1,33 @@
import React, { useEffect, useReducer } from 'react'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import {
INSERT_ORDERED_LIST_COMMAND,
INSERT_UNORDERED_LIST_COMMAND,
REMOVE_LIST_COMMAND,
} from '@lexical/list'
import type { ChangeEvent } from 'react'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { $createHeadingNode } from '@lexical/rich-text'
import { $setBlocksType } from '@lexical/selection'
import {
$createParagraphNode,
$getSelection,
$isRangeSelection,
FORMAT_TEXT_COMMAND,
} from 'lexical'
import { $createHeadingNode } from '@lexical/rich-text'
import { $setBlocksType } from '@lexical/selection'
import type { ChangeEvent } from 'react'
import React, { useEffect, useReducer } from 'react'

import {
registerLexicalEvents,
synchronizeToolbarState,
toolbarStateReducer,
} from '../LexicalEditor/utils'
import type {
CustomEmojiGroup,
EditorPlugin,
Emoji,
} from '../LexicalEditor/types'
import {
INSERT_CUSTOM_EMOJI_COMMAND,
INSERT_EMOJI_COMMAND,
} from '../plugins/EmojiPlugin/commands'
import type { HeaderValue } from '../RichTextEditorToolbar'
import RichTextEditorToolbar, {
ALLOWED_HEADER_TYPE,
} from '../RichTextEditorToolbar'

type Props = {
customEmojis?: CustomEmojiGroup[]
disabled?: boolean
toolbarRef: React.RefObject<HTMLDivElement>
plugins?: EditorPlugin[]
testIds?: {
wrapper?: string
editor?: string
Expand All @@ -53,8 +42,6 @@ type Props = {
const LexicalEditorToolbarPlugin = ({
disabled = false,
toolbarRef,
customEmojis,
plugins,
testIds,
}: Props) => {
const [editor] = useLexicalComposerContext()
Expand Down Expand Up @@ -94,24 +81,6 @@ const LexicalEditorToolbarPlugin = ({
)
}

const handleInsertEmoji = (emoji: Emoji) => {
const isNativeEmoji = emoji.native
const isCustomEmoji = emoji.src

if (isNativeEmoji) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
editor.dispatchCommand(INSERT_EMOJI_COMMAND, emoji.native!)
}

if (isCustomEmoji) {
editor.dispatchCommand(INSERT_CUSTOM_EMOJI_COMMAND, {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
src: emoji.src!,
id: emoji.id,
})
}
}

const handleHeaderClick = ({
target: { value },
}: ChangeEvent<{
Expand All @@ -138,17 +107,13 @@ const LexicalEditorToolbarPlugin = ({
list,
header,
}}
id='toolbar'
onUnorderedClick={handleUnorderedClick}
onOrderedClick={handleOrderedClick}
onBoldClick={handleBoldClick}
onItalicClick={handleItalicClick}
onHeaderChange={handleHeaderClick}
disabled={disabled}
onInsertEmoji={handleInsertEmoji}
ref={toolbarRef}
customEmojis={customEmojis}
plugins={plugins}
testIds={testIds}
/>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import React, { forwardRef, useRef, useState, useCallback } from 'react'
import type { Theme } from '@material-ui/core/styles'
import { makeStyles } from '@material-ui/core/styles'
import type { OutlinedInputStatus } from '@toptal/picasso'
import { InputMultilineAdornment } from '@toptal/picasso'
import type { BaseProps } from '@toptal/picasso-shared'
import { useHasMultilineCounter } from '@toptal/picasso-shared'
import cx from 'classnames'
import { noop, usePropDeprecationWarning } from '@toptal/picasso/utils'
import { InputMultilineAdornment } from '@toptal/picasso'
import type { OutlinedInputStatus } from '@toptal/picasso'
import cx from 'classnames'
import React, { forwardRef, useCallback, useRef, useState } from 'react'

import styles from './styles'
import { useCounter } from './hooks'
import type { ASTType } from '../RichText'
import type { CounterMessageSetter } from './types'
import LexicalEditor from '../LexicalEditor'
import type {
ChangeHandler,
CustomEmojiGroup,
EditorPlugin,
LexicalEditorProps,
} from '../LexicalEditor'
import LexicalEditor from '../LexicalEditor'
import type { ASTType } from '../RichText'
import { useCounter } from './hooks'
import styles from './styles'
import type { CounterMessageSetter } from './types'

export interface Props extends BaseProps {
/** Indicates that an element is to be focused on page load */
Expand Down Expand Up @@ -86,7 +86,7 @@ export interface Props extends BaseProps {
orderedListButton?: string
}
highlight?: 'autofill'
customEmojis?: CustomEmojiGroup[]
customEmojis?: LexicalEditorProps['customEmojis']
}

const useStyles = makeStyles<Theme>(styles, {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { OmitInternalProps } from '@toptal/picasso-shared'

import type { Props } from './RichTextEditor'
export type { CustomEmojiGroup, RichTextEditorChangeHandler } from './types'
export type { RichTextEditorChangeHandler } from './types'

export type CustomEmojiGroup = Exclude<Props['customEmojis'], undefined>[0]

export { default } from './RichTextEditor'
export type RichTextEditorProps = OmitInternalProps<Props>
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import React, { useState } from 'react'
import { Container } from '@toptal/picasso'
import { RichTextEditor } from '@toptal/picasso-rich-text-editor'

import type { RichTextEditorChangeHandler, CustomEmojiGroup } from '../types'
import type { RichTextEditorChangeHandler } from '../types'
import type { RichTextEditorProps } from '..'

const customEmojis = [
const customEmojis: RichTextEditorProps['customEmojis'] = [
{
id: 'talent-community',
name: 'Talent Community',
Expand All @@ -21,7 +22,7 @@ const customEmojis = [
},
],
},
] as CustomEmojiGroup[]
]

const Example = () => {
const [value, setValue] = useState<string | undefined>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,3 @@ export type {
ChangeHandler as RichTextEditorChangeHandler,
TextLengthChangeHandler,
} from '../LexicalEditor'

export type { CustomEmojiGroup, CustomEmoji } from '../LexicalEditor'
Loading
Loading