DevGang
Авторизоваться

SlateJS: Создание текстового редактора 

Настройка текстового редактора может улучшить взаимодействие с пользователем. Slate - это структура текстового редактора, которую можно настроить в соответствии с вашими потребностями.

Установка Slate

Нам понадобится три модуля для нашей реализации Slate.

  1. slate: Основной модуль Slate
  2. slate-react: Обертка React для Slate
  3. slate-history: Позволяет пользователю отменять свои действия.
npm i --save slate slate-react slate-history

или же

yarn add slate slate-react slate-history

Настройка Slate

Чтобы создать новый редактор, используйте createEditor в сочетании с парой плагинов - withReact и withHistory.

import { createEditor } from 'slate';
import { withReact } from 'slate-react';
import { withHistory } from 'slate-history';

const Editor = () => {
  const editor = useMemo(() => withReact(withHistory(createEditor())), []);
  ...
}

Затем для визуализации редактора Slate будут использоваться два компонента.

import { Slate, Editable, ... } 'slate-react';

const Editor = () => {
  ...
  return (
    <Slate editor={editor}>
      <Editable />
    </Slate>
  )
}

Добавление содержимого

Первоначально Slate вылетал из строя, потому что необходимо значение по умолчанию.

const Editor = () => {
  ...
  const [value, setValue] = useState([
    {
      children: [{ text: 'This is my paragraph!' }]
    }
  ])

  return (
    <Slate ... value={value} setValue={setValue}>
      ...
    </Slate>
  )
}

Важно, чтобы существовал элемент по умолчанию, а не просто пустой массив. Наличие пустого массива приведет к сбою, поскольку Slate не к чему прикрепить курсор.

Пользовательские типы

По умолчанию содержимое будет рассматриваться как текст, но редакторы Rich Text могут иметь нетекстовое содержимое.

Каждый элемент может иметь настраиваемые свойства, помогающие визуализировать указанный элемент.

const [value, setValue] = useState([
  {
    type: 'paragraph',
    children: [{ text: 'This is my paragraph' }]
  },
  {
    type: 'image',
    src: 'path/to/image',
    alt: 'This is my image'
    children: [{ text: '' }]
  }
])

Затем мы можем визуализировать эти пользовательские элементы, используя параметр renderElement компонента Editor.

const Paragraph = ({ attributes, children }) => (
  <p {...attributes}>{children}</p>
)

const Image = ({ attributes, element, children }) => (
  <div {...attributes}>
    <div contentEditable={false}>
      <img src={element.src} alt={element.src} />
    </div>
    {children}
  </div>
)

const renderElement = (props) => {
  switch(props.element.type) {
    case 'image': 
      return <Image {...props} />
    default:
      return <Paragraph {...props} />
  }
}

const Editor = () => {
  ...
  return (
    <Slate ...>
      <Editor renderElement={renderElement} />
    </Slate>
  )
}

Важно, чтобы каждый элемент отображал параметр children, поскольку именно так Slate может отслеживать, какой элемент в данный момент находится в фокусе.

Voids

Voids - это элементы, которые нельзя редактировать, как если бы это был текст. Поскольку наше изображение нельзя редактировать, как если бы оно было текстом, нам нужно сообщить Slate, что это недействительный элемент.

Плагины

Созданный нами объект editor имеет функцию isVoid, которая определяет, является ли элемент недействительным или нет.

Slate позволяет нам создавать плагины, которые могут изменять функциональность существующих функций editor или добавлять новые.

const withImage = (editor) => {
  const { isVoid } = editor;

  editor.isVoid = (element) =>
    element.type === 'image' ? true : isVoid(element);

  return editor;
}

const Editor = () => {
  const editor = useMemo(() => withReact(withHistory(withImages(createEditor()))), []);
  ...
}

СОВЕТ: Поскольку у вас может быть много плагинов, особенно для более сложных редакторов, вы можете использовать функцию pipe из lodash/fp.

import pipe from 'lodash/fp/pipe'

const createEditorWithPlugins = pipe(
  withReact,
  withHistory,
  withImage
)

const Editor = () => {
  const editor = useMemo(() => createEditorWithPlugins(createEditor()), []);
}

Обработка событий

Поскольку Image теперь считается пустым элементом, он теряет некоторые функциональные возможности клавиатуры. К счастью, редактор предоставляет нам две функции, которые мы можем расширить.

insertBreak

Функция editor.insertBreak вызывается, когда пользователь нажимает enter или return для Mac.

const { isVoid, insertBreak, ... } = editor

editor.insertBreak = (...args) => {
  const parentPath = Path.parent(editor.selection.focus.path);
  const parentNode = Node.get(editor, parentPath);

  if (isVoid(parentNode)) {
    const nextPath = Path.next(parentPath);
    Transforms.insertNodes(
      editor,
      {
        type: 'paragraph',
        children: [{ text: '' }]
      }, 
      {
        at: nextPath,
        select: true // Focus on this node once inserted
      }
    );
  } else {
    insertBreak(...args);
  }
}

deleteBackward

Функция editor.deleteBackward вызывается, когда пользователь нажимает backspace или delete для Mac.

const { isVoid, deleteBackward, ... } = editor

editor.deleteBackward = (...args) => {
  const parentPath = Path.parent(editor.selection.focus.path);
  const parentNode = Node.get(editor, parentPath);

  if (isVoid(parentNode) || !Node.string(parentNode).length) {
    Transforms.removeNodes(editor, { at: parentPath });
  } else {
    deleteBackward(...args);
  }
}

Заключение

Как видите, Slate можно гибко настраивать, поскольку он дает вам необходимые инструменты для добавления собственных функций.

Далее: Как добавить изображение и ссылки в SlateJS

#React
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

Поделитесь своим опытом, расскажите о новом инструменте, библиотеке или фреймворке. Для этого не обязательно становится постоянным автором.

Попробовать

В подарок 100$ на счет при регистрации

Получить