import React, { useCallback, useMemo } from 'react' import isHotkey from 'is-hotkey' import { Editable, withReact, useSlate, Slate } from 'slate-react' import { Editor, Transforms, createEditor, Descendant, Element as SlateElement, } from 'slate' import { withHistory } from 'slate-history' import { Button, Icon, Toolbar } from '../components' const HOTKEYS = { 'mod+b': 'bold', 'mod+i': 'italic', 'mod+u': 'underline', 'mod+`': 'code', } const LIST_TYPES = ['numbered-list', 'bulleted-list'] const TEXT_ALIGN_TYPES = ['left', 'center', 'right', 'justify'] const RichTextExample = () => { const renderElement = useCallback(props => , []) const renderLeaf = useCallback(props => , []) const editor = useMemo(() => withHistory(withReact(createEditor())), []) return ( { for (const hotkey in HOTKEYS) { if (isHotkey(hotkey, event as any)) { event.preventDefault() const mark = HOTKEYS[hotkey] toggleMark(editor, mark) } } }} /> ) } const toggleBlock = (editor, format) => { const isActive = isBlockActive( editor, format, TEXT_ALIGN_TYPES.includes(format) ? 'align' : 'type' ) const isList = LIST_TYPES.includes(format) Transforms.unwrapNodes(editor, { match: n => !Editor.isEditor(n) && SlateElement.isElement(n) && LIST_TYPES.includes(n.type) && !TEXT_ALIGN_TYPES.includes(format), split: true, }) let newProperties: Partial if (TEXT_ALIGN_TYPES.includes(format)) { newProperties = { align: isActive ? undefined : format, } } else { newProperties = { type: isActive ? 'paragraph' : isList ? 'list-item' : format, } } Transforms.setNodes(editor, newProperties) if (!isActive && isList) { const block = { type: format, children: [] } Transforms.wrapNodes(editor, block) } } const toggleMark = (editor, format) => { const isActive = isMarkActive(editor, format) if (isActive) { Editor.removeMark(editor, format) } else { Editor.addMark(editor, format, true) } } const isBlockActive = (editor, format, blockType = 'type') => { const { selection } = editor if (!selection) return false const [match] = Array.from( Editor.nodes(editor, { at: Editor.unhangRange(editor, selection), match: n => !Editor.isEditor(n) && SlateElement.isElement(n) && n[blockType] === format, }) ) return !!match } const isMarkActive = (editor, format) => { const marks = Editor.marks(editor) return marks ? marks[format] === true : false } const Element = ({ attributes, children, element }) => { const style = { textAlign: element.align } switch (element.type) { case 'block-quote': return (
{children}
) case 'bulleted-list': return (
    {children}
) case 'heading-one': return (

{children}

) case 'heading-two': return (

{children}

) case 'list-item': return (
  • {children}
  • ) case 'numbered-list': return (
      {children}
    ) default: return (

    {children}

    ) } } const Leaf = ({ attributes, children, leaf }) => { if (leaf.bold) { children = {children} } if (leaf.code) { children = {children} } if (leaf.italic) { children = {children} } if (leaf.underline) { children = {children} } return {children} } const BlockButton = ({ format, icon }) => { const editor = useSlate() return ( ) } const MarkButton = ({ format, icon }) => { const editor = useSlate() return ( ) } const initialValue: Descendant[] = [ { type: 'paragraph', children: [ { text: 'This is editable ' }, { text: 'rich', bold: true }, { text: ' text, ' }, { text: 'much', italic: true }, { text: ' better than a ' }, { text: '