Skip to content

[지호] 문서에디터 (react‐draft‐wysiwyg)

BaeJ1H021 edited this page Dec 11, 2023 · 1 revision

질문 등록을 위한 문서에디터 적용하기

문서 에디터로는 react-draft-wysiwyg를 채택하였다.

참고로 아래와 같이 react-draft-wysiwyg 뿐만 아니라 draft-js도 설치해줘야 한다. (이유를 찾아보니 draft-js를 통해 DOM에 텍스트를 넣는다고 한다)

npm install react-draft-wysiwyg draft-js

image

이 정도 간단한 세팅을 하였는데 아래와 같은 오류가 발생하였다.

image

React and Electron with Draft.js: "Global is not defined"

위에 어떤 귀인분께서 해결책을 알려주셨다.

이제 아래의 명령어를 통해 css를 가져오면 된다.

import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css'

image

위와 같이 문서에디터에 틀이 보이기 시작한다.

하지만 또 아래와 같은 문제가 생겼다.

Warning: Can't call setState on a component that is not yet mounted. This is a no-op, but it might indicate a bug in your application. Instead, assign to this.state directly or define a state = {}; class property with the desired state in the i3 component.

Can't call setState on a component that is not yet mounted · Issue #861 · google-map-react/google-map-react

해결책은 React.StrictMode를 제거하는 것이다.

image

위와 같이 React.StrictMode를 제거하였다.

image

마침내 문서에디터가 정상적으로 작동한다. 야호!

image

image

위와 같이 convertToRaw 함수 안에 인자로 editorState.getCurrrentContent()를 넣어주면

image

이런식으로 결과값이 나온다. 우리는 html 상태로 위의 객체를 변환시켜야 하기 때문에

npm install draftjs-to-html

위의 라이브러리에 draftToHtml 함수를 통해 html 상태로 변환한다.

image

image

image

위와 같이 원하는 html 태그 형식으로 값을 저장할 수 있게 되었다.

import { EditorState, convertToRaw } from 'draft-js';
import { Editor } from 'react-draft-wysiwyg';
import { useState } from 'react';
import { EditorWrapper } from './DocumentEditor.styles';
import 'react-draft-wysiwyg/dist/react-draft-wysiwyg.css';
import draftToHtml from 'draftjs-to-html';

const DocumentEditor = () => {
  const [editorState, setEditorState] = useState(EditorState.createEmpty());

  const onEditorStateChange = (newEditorState: EditorState) => {
    setEditorState(newEditorState);
  };

  const handleSave = (event: Event) => {
    event.preventDefault(); // 폼 제출의 기본 동작을 막음
    const contentState = draftToHtml(
      convertToRaw(editorState.getCurrentContent()),
    );
    // eslint-disable-next-line no-console
    console.log(contentState);
  };

  return (
    <EditorWrapper>
      <Editor
        // 에디터와 툴바 모두에 적용되는 클래스
        wrapperClassName="wrapper-class"
        // 에디터에만 적용되는 클래스
        editorClassName="editor"
        // 툴바에 적용되는 클래스
        toolbarClassName="toolbar-class"
        // 툴바 설정
        toolbar={{
          list: { inDropdown: true },
          textAlign: { inDropdown: true },
          link: { inDropdown: true },
          history: { inDropdown: false },
        }}
        
        placeholder="내용을 입력해주세요."
        // 언어 설정
        localization={{
          locale: 'ko',
        }}
        // 에디터에서 어떤 상태를 다룰 지 설정
        editorState={editorState}
        // 에디터 값 변경 감지
        onEditorStateChange={onEditorStateChange}
      />
      <button style={{ width: '100px', height: '100px' }} onClick={handleSave}>
        click me
      </button>
    </EditorWrapper>
  );
};

export default DocumentEditor;

이미지 업로드를 위한 uploadCallback 추가

이미지를 업로드하기 위해서는 uploadCallback 함수를 추가해주어야 한다.

const DocumentEditor = ({
  editorState,
  setEditorState,
}: EditorProps) => {
  const onEditorStateChange = (newEditorState: EditorState) => {
    setEditorState(newEditorState);
  };

const uploadImageToNaverCloudPlatform = async (file: FileProps) => {
    const bucket = 'algocean';
    // 이미지를 업로드할 년 월 일 폴더 생성
    const currentDate = new Date();
    const year = currentDate.getFullYear();
    const month = (currentDate.getMonth() + 1).toString().padStart(2, '0');
    const day = currentDate.getDate().toString().padStart(2, '0');
    const folder_path = `images/${year}/${month}/${day}`;
    const object_name =
      uuidv4().toString().replace(/-/g, '') +
      file.name.replace(/[-{}^%`[\]">~<#|]/g, '');
    // 파일 업로드
    try {
      await S3.putObject({
        Bucket: bucket,
        Key: folder_path + '/' + object_name,
        ACL: 'public-read',
        // ACL을 지우면 전체 공개되지 않습니다.
        Body: file,
      }).promise();
    } catch (error) {
      console.error('에러 발생', error);
    }
    return `https://kr.object.ncloudstorage.com/${bucket}/${folder_path}/${object_name}`;
  };

  // 이미지 업로드
  const uploadCallback = async (file: FileProps) => {
    try {
      const filePath = await uploadImageToNaverCloudPlatform(file);
      const contentState = editorState.getCurrentContent();
      const contentStateWithEntity = contentState.createEntity(
        'IMAGE',
        'IMMUTABLE',
        { src: filePath },
      );
      const entityKey = contentStateWithEntity.getLastCreatedEntityKey();
      const newEditorState = EditorState.set(editorState, {
        currentContent: contentStateWithEntity,
      });
      setEditorState(
        AtomicBlockUtils.insertAtomicBlock(newEditorState, entityKey, ' '),
      );
    } catch (error) {
      console.error('에러 발생', error);
    }
  };

  return (
    <EditorWrapper>
      <Editor
        // 에디터와 툴바 모두에 적용되는 클래스
        wrapperClassName="wrapper-class"
        // 에디터에만 적용되는 클래스
        editorClassName="editor"
        // 툴바에 적용되는 클래스
        toolbarClassName="toolbar-class"
        // 툴바 설정
        toolbar={{
          list: { inDropdown: true },
          textAlign: { inDropdown: true },
          link: { inDropdown: true },
          history: { inDropdown: false },
          image: { uploadCallback: uploadCallback }, // 이미지 업로드
        }}
        placeholder="내용을 입력해주세요."
        // 언어 설정
        localization={{
          locale: 'ko',
        }}
        // 에디터에서 어떤 상태를 다룰 지 설정
        editorState={editorState}
        // 에디터 값 변경 감지
        onEditorStateChange={onEditorStateChange}
      />
    </EditorWrapper>
  );
};

export default DocumentEditor;

uploadImageToNaverCloudPlatform 함수를 통해 Naver Cloud Platform에 동적으로 생성한 url에파일을 업로드한다. uploadCallback 함수에서 해당 url을 받아와서 기존 editorState에 이미지를 추가해준다.

image

🌊 ALGOCEAN

TEAM : 강서(대문)구

기획

아키텍처

스프린트 계획회의

데일리스크럼

팀 회고

개발 일지

태호

more

지호

more

지은

more

승규

more

멘토링 일지

Clone this wiki locally