Как реализовать drag and drop в редакторе текста с помощью draft-js?

Доброго времени суток, возникла проблема с реализацией drag and drop в реадкторе текста, который я реализовал через draft-js. Добавил функции форматирования текста, заголовков, так же добавление ссылки и картинки. И появилась задача в реализации drag and drop в draft-js. Подскажите как это можно сделать?

Вот мой функционал редактора
import {
  AtomicBlockUtils,
  DraftEntityMutability,
  EditorState,
  RichUtils,
} from "draft-js";
import React, { Dispatch, SetStateAction } from "react";
import { BlockType, EntityType, InlineStyle } from "./config.ts";
import { IMMUTABLE, MUTABLE } from "../../../Contstants/SettingsEntityType.ts";

export type EditorApi = {
  state: EditorState;
  onChange: (state: EditorState) => void;
  toggleBlockType: (blockType: BlockType) => void;
  toggleInlineStyle: (inlineStyle: InlineStyle) => void;
  isCursorInBold: (type: InlineStyle) => boolean;
  addLink: (url: string) => void;
  setEntityData: (entityKey: string, data: Record<string, string>) => void;
  addImage: (url: string) => void;
};

export const useEditor = (
  state: EditorState,
  setState: Dispatch<SetStateAction<EditorState>>,
): EditorApi => {
  // Функция для стилистики шрифтов
  const toggleBlockType = React.useCallback(
    (blockType: BlockType) => {
      setState((currentState) =>
        RichUtils.toggleBlockType(currentState, blockType),
      );
    },
    [setState],
  );

  // Функция для стилистики текста
  const toggleInlineStyle = React.useCallback(
    (inlineStyle: InlineStyle) => {
      setState((currentState) =>
        RichUtils.toggleInlineStyle(currentState, inlineStyle),
      );
    },
    [setState],
  );

  // Функция для проверки наличия полужирного стиля
  const isCursorInBold = React.useCallback(
    (type: InlineStyle) => {
      const currentStyle = state.getCurrentInlineStyle();
      return currentStyle.has(type);
    },
    [state],
  );

  // Функция обработки добавления сущности в редактор
  const addEntityToEditor = React.useCallback(
    (
      entityType: EntityType,
      data: Record<string, string>,
      mutability: DraftEntityMutability,
      toggleLink: boolean,
    ) => {
      setState((currentState) => {
        // Получаем текущий контент
        const contentState = currentState.getCurrentContent();
        // Создаем Entity с данными
        const contentStateWithEntity = contentState.createEntity(
          entityType,
          mutability,
          data,
        );
        // Получаем уникальный ключ Entity
        const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
        // Объединяем текущее состояние с новым
        const newState = EditorState.set(currentState, {
          currentContent: contentStateWithEntity,
        });

        if (toggleLink) {
          // Вставляем ссылку в указанное место
          return RichUtils.toggleLink(
            newState,
            newState.getSelection(),
            entityKey,
          );
        } else {
          // Вставляем сущность в указанное место
          return AtomicBlockUtils.insertAtomicBlock(newState, entityKey, " ");
        }
      });
    },
    [setState],
  );

  // Функция обработки добавления сущности ссылки в редактор
  const addEntity = React.useCallback(
    (
      entityType: EntityType,
      data: Record<string, string>,
      mutability: DraftEntityMutability,
    ) => addEntityToEditor(entityType, data, mutability, true),
    [addEntityToEditor],
  );

  // Функция обработки добавления сущности картинки в редактор
  const addEntityImage = React.useCallback(
    (
      entityType: EntityType,
      data: Record<string, string>,
      mutability: DraftEntityMutability,
    ) => addEntityToEditor(entityType, data, mutability, false),
    [addEntityToEditor],
  );

  // Функция добавления ссылки в редактор
  const addLink = React.useCallback(
    (url: string) => addEntity(EntityType.link, { url }, MUTABLE),
    [addEntity],
  );

  // Функция добавления картинки в редактор
  const addImage = React.useCallback(
    (url: string) => addEntityImage(EntityType.image, { url }, IMMUTABLE),
    [addEntityImage],
  );

  const setEntityData = React.useCallback(
    (entityKey: string, data: Record<string, string>) => {
      setState((currentState) => {
        const content = currentState.getCurrentContent();
        const contentStateUpdated = content.mergeEntityData(entityKey, data);
        return EditorState.push(
          currentState,
          contentStateUpdated,
          "apply-entity",
        );
      });
    },
    [setState],
  );

  return React.useMemo(
    () => ({
      state,
      onChange: setState,
      toggleBlockType,
      toggleInlineStyle,
      isCursorInBold,
      addLink,
      setEntityData,
      addImage,
    }),
    [
      state,
      toggleBlockType,
      toggleInlineStyle,
      isCursorInBold,
      addLink,
      setEntityData,
      setState,
      addImage,
    ],
  );
};


Теперь пытаюсь разобраться, как сдлать функционал drag and drop

Вот сам Editor
import { Editor } from "draft-js";
import { useEditorApi } from "../TextEditorFunctions/useEditorApi.ts";
import classes from "./TextEditor.module.scss";
import { useEffect, useRef } from "react";

const TextEditor = () => {
  const editorApi = useEditorApi();
  const ref = useRef<Editor | null>(null);

  useEffect(() => {
    if (ref.current) ref.current.focus(); // Установка фокуса при изменении контента
  }, [editorApi]);

  if (!editorApi) return null;

  return (
    <div className={classes.textArea}>
      <Editor
        ref={ref}
        placeholder={"Описание поста"}
        editorState={editorApi.state}
        onChange={editorApi.onChange}
      />
    </div>
  );
};

export default TextEditor;
  • Вопрос задан
  • 62 просмотра
Решения вопроса 1
@Brepex Автор вопроса
Для того что бы реализовать drag and drop, нужно реализовать вот ткую вот функцию
const handlerDropAndDrop: DragEventHandler = async (
    event: DragEvent<HTMLDataElement>,
  ) => {
    event.preventDefault();
    const url = event.dataTransfer.getData("URL"); // Получаем src из фото, после чего закидываем его в функцию добавления картинки
    if (url) editorApi.addImage(url);
  };


Но на данном этапе картинка будет просто дублироваться, что бы не было дублирования картинки, нужно добавить функцию удаления картинки, но перед этим обновим шаблон картинки
import { ContentState } from "draft-js";
import * as React from "react";
import classes from "./Image.module.scss";

type LinkProps = {
  contentState: ContentState;
  entityKey: string;
};

const Image: React.FC<LinkProps> = ({ contentState, entityKey }) => {
  /* Получаем url с помощью уникального ключа Entity */
  const { url } = contentState.getEntity(entityKey).getData();

  return (
    <img data-key={entityKey} className={classes.imageStyle} src={url} alt="" />
  );
};
export default Image;


Добавим data-key={entityKey} через который мы будем получать entityKey и через него же будем удалять картинку. Добавим еще одну функцию обработчик
const handlerDragAndDropExit: DragEventHandler = (
    event: DragEvent<HTMLDivElement>,
  ) => {
    event.preventDefault();
    const targetElement = event.target as HTMLElement; // Получаем тег img
    const entityKey = targetElement.getAttribute("data-key"); // Получаем у него наш entityKey
    if (entityKey) editorApi.removeImage(entityKey); передаем его в функцию удаления картинки
  };


Функция удаления картинки которая находится в useEditor
// Удаление картинки по его entityKey
  const removeImage = React.useCallback(
    (entityKey: string) => {
      setState((currentState) => {
        const contentState = currentState.getCurrentContent();
        const blockMap = contentState.getBlockMap();

        // Создаем новый массив блоков, исключая блок с указанным entityKey или блок без типа EntityType.image
        const newBlockMap: ContentBlock[] = [];
        blockMap.forEach((block) => {
          if (
            block &&
            (block.getEntityAt(0) !== entityKey || block.getType() !== "atomic")
          ) {
            newBlockMap.push(block);
          }
        });

        const newContentState = ContentState.createFromBlockArray(newBlockMap);

        return EditorState.push(currentState, newContentState, "remove-range");
      });
    },
    [setState],
  );


Осталось только добавить все это в наши обработчики дива
<div
      className={classes.textArea}
      onDrop={handlerDropAndDrop}
      onDragEnd={handlerDragAndDropExit}
    >
      <Editor
        ref={ref}
        placeholder={"Описание поста"}
        editorState={editorApi.state}
        onChange={editorApi.onChange}
      />
    </div>


Готово, функция drag and drop реализована
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы