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

SlateJS: добавление изображений и ссылок 

Ранее мы говорили о настройке простого текстового редактора SlateJS. Теперь мы собираемся добавить в наш текстовый редактор две новые функции - вставку изображения и ссылки.

Панель инструментов

Чтобы начать добавлять функциональные возможности форматированного текста, нам нужно создать компонент панели инструментов.

import { useSlateStatic } from 'slate-react';
...

const Toolbar = () => {
  const editor = useSlateStatic()

  const handleInsertImage = () => {
    const url = prompt("Enter an Image URL"); // For simplicity
    insertImage(editor, url); // will be implemented later
  };

  const handleInsertLink = () => {
    const url = prompt("Enter a URL"); // For simplicity
    insertLink(editor, url); // will be implemented later
  };

  return (
    <div className="toolbar">
      <button onClick={handleInsertImage}>Image</button>
      <button onClick={handleInsertLink}>Link</button>
    </div>
  )
}

Здесь следует отметить следующие важные моменты:

  1. useSlateStatic: дает нам экземпляр нашего редактора, который не вызывает повторного рендеринга,
  2. insertImage: вспомогательная функция, которая вставит изображение в наш редактор
  3. insertLink: вспомогательная функция, которая вставит ссылку в наш редактор

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

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

insertImage

Наша функция insertImage будет обрабатывать то, как мы будем вставлять изображения в наш редактор. Придется установить некоторые правила.

  1. Если редактор не имеет фокуса, мы добавим изображение в конец редактора.
  2. Если редактор сосредоточен на пустом узле (например, узле изображения), мы заменим пустой узел изображением.
  3. Если редактор сосредоточен на непустом узле, мы добавим изображение после него.
const insertImage(editor, url) => {
   if (!url) return;

  const { selection } = editor;
  const image = createImageNode("Image", url);

  ReactEditor.focus(editor);

  if (!!selection) {
    const [parentNode, parentPath] = Editor.parent(
      editor,
      selection.focus?.path
    );

    if (editor.isVoid(parentNode) || Node.string(parentNode).length) {
      // Вставьте новый узел изображения после пустого узла или узла с содержимым
      Transforms.insertNodes(editor, image, {
        at: Path.next(parentPath),
        select: true
      });
    } else {
      // Если узел пуст, замените его
      Transforms.removeNodes(editor, { at: parentPath });
      Transforms.insertNodes(editor, image, { at: parentPath, select: true });
    }
  } else {
    // Вставьте новый узел изображения в нижней части редактора при выделении
    Transforms.insertNodes(editor, image, { select: true });
  }
}

insertLink

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

  1. Если редактор не сфокусирован, вставьте новую ссылку внутри абзаца в конце редактора.
  2. Если редактор сосредоточен на пустом узле (например, на узле изображения), вставьте новую ссылку внутри абзаца под пустым узлом.
  3. Если редактор находится внутри абзаца, вставьте новую ссылку в выбранное место.
  4. Если выделен диапазон текста, преобразуйте выделенный текст в ссылку.
  5. Если выделенный текст состоит из ссылки, удалите ссылку и следуйте Правилам № 3 и № 4.
const createLinkNode = (href, text) => ({
  type: "link",
  href,
  children: [{ text }]
});

const removeLink = (editor, opts = {}) => {
  Transforms.unwrapNodes(editor, {
    ...opts,
    match: (n) =>
      !Editor.isEditor(n) && Element.isElement(n) && n.type === "link"
  });
};

const insertLink = (editor, url) => {
  if (!url) return;

  const { selection } = editor;
  const link = createLinkNode(url, "New Link");

  ReactEditor.focus(editor);

  if (!!selection) {
    const [parentNode, parentPath] = Editor.parent(
      editor,
      selection.focus?.path
    );

    if (parentNode.type === "link") {
      removeLink(editor);
    }

    if (editor.isVoid(parentNode)) {
      Transforms.insertNodes(editor, createParagraphNode([link]), {
        at: Path.next(parentPath),
        select: true
      });
    } else if (Range.isCollapsed(selection)) {
      Transforms.insertNodes(editor, link, { select: true });
    } else {
      Transforms.wrapNodes(editor, link, { split: true });
      Transforms.collapse(editor, { edge: "end" });
    }
  } else {
    Transforms.insertNodes(editor, createParagraphNode([link]));
  }
};

Пользовательский тип&nbsp;Link

Теперь, когда мы можем вставлять ссылки, давайте убедимся, что мы можем правильно отображать ссылку.

const Link = ({ attributes, element, children }) => (
  <a {...attributes} href={element.href}>
    {children}
  </a>
);

Затем мы можем обновить нашу функцию renderElement, чтобы включить новый тип ссылки.

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

Всплывающее окно ссылки

Поскольку мы не можем точно сказать, какой URL у ссылки, мы можем создать простое всплывающее окно всякий раз, когда мы фокусируемся на ссылке. Мы можем сделать это, обновив наш компонент Link.

const Link = ({ attributes, element, children }) => {
  const editor = useSlateStatic();
  const selected = useSelected();
  const focused = useFocused();

  return (
    <div className="element-link">
      <a {...attributes} href={element.href}>
        {children}
      </a>
      {selected && focused && (
        <div className="popup" contentEditable={false}>
          <a href={element.href} rel="noreferrer" target="_blank">
            <FontAwesomeIcon icon={faExternalLinkAlt} />
            {element.href}
          </a>
          <button onClick={() => removeLink(editor)}>
            <FontAwesomeIcon icon={faUnlink} />
          </button>
        </div>
      )}
    </div>
  );
};

Заключение

Когда наш форматированный текст обретает форму, мы начинаем видеть мощь Slate и то, как вы можете реализовать его так, как вы хотите.

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

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

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

Попробовать

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

Получить