toast ui editor api 사용, 마크다운 서버 전송, 마크다운 화면 렌더링

Suxxzzy.log·2022년 4월 9일
2

홀로서기

목록 보기
1/1

에디터를 화면에 붙여보자

  1. 토스트 에디터 모듈부터 설치해보았다.
    JS 말고 리액트 컴포넌트 형태로 출시된 모듈이 있어서 설치해보았다.
npm install --save @toast-ui/react-editor
//리액트 버전으로 인한 에러 발생 시 다음 명령어로 설치
npm install --save @toast-ui/react-editor --legacy-peer-deps

=> 이렇게 했더니 나는 18버전과 관련한 에러가 떴다. 18버전에 대한 에러 이슈는 검색해도 나오질 않아서, 터미널에서 말하는 대로 api가 지원하는 리액트 17.0.1버전으로 다운그레이드 하였다.

cra 설치 후 버전 다운그레이드:

npm install --save react@^17.0.1 react-dom@17.0.1 

이후 토스트 에디터 모듈 설치하여 성공하였다.
리액트 버전을 낮춰 개발해야 할 것 같다.

또한 이 모듈은 CSS 파일을 가지고 있지 않기 때문에 toastui-editor.css 를 @toast-ui/editor 에서 가지고 와야 한다.

import '@toast-ui/editor/dist/toastui-editor.css';

에디터 컴포넌트는 다음과 같이 가지고 오며, 여러 가지 옵션들(에디터 창에 추가하는 글씨 볼드체 만들기, 리스트 점 찍기 등등)은 Editor 컴포넌트에 프롭스로 내려주면 된다.

import { Editor } from '@toast-ui/react-editor';

에디터옵션(크기, 플레이스홀더값, 스타일, 에디터타입 등)은 Editor라고 하는 컴포넌트에 프롭스로 내려주면 된다.

const MyComponent = () => (
<Editor
initialValue="hello react editor world!"
previewStyle="vertical"
height="600px"
initialEditType="markdown"
useCommandShortcut={true}
/>
);

에디터에다가 툴바 메뉴 추가하는 등, api문서에 제시된 api들을 쓰려면 getInstance()를 꼭 써야 한다. 이때 useRef()를 이용해 에디터에 대한 참조값을 가져와야 한다.

class MyComponent extends React.Component {
editorRef = React.createRef();

handleClick = () => {
this.editorRef.current.getInstance().exec('Bold');
};

에디터를 감싸는 엘리먼트를 얻으려면 getRootElement()사용한다.

editorRef = React.createRef();

handleClickButton = () => {
this.editorRef.current.getRootElement().classList.add('my-editor-root');
};

에디터에 대한 이벤트 추가 시 카멜케이스로 이벤트 이름 쓴다. on[이벤트명] 형식이다.

class MyComponent extends React.Component {
handleFocus = () => {
console.log('focus!!');
};

render() {
return (
<Editor
previewStyle="vertical"
height="400px"
initialEditType="markdown"
initialValue="hello"
ref={this.editorRef}
onFocus={this.handleFocus}
/>
);
}
}

onFocus: 에디터 클릭했을 때 자동으로 강조 표시됨..

npm start 시 특정 파일이 없다고 에러가 떴는데, npm start 스크립트를 아래와 같이 수정한다.

"scripts": {
"start": "GENERATE_SOURCEMAP=false react-scripts start",
...
},

레퍼런스에 의하면 리액트 스크립트 버전 문제인 것으로 보인다.

이제 에디터를 화면에서 볼 수 있다!
기본 마크다운 문법이 사용 가능했다.

서버에게 마크다운 형식 데이터를 그대로 보내려면?

다만, 이것을 서버에게 마크다운 형식으로 보내주는 것이 문제였다.
토스트 에디터 공식 문서를 더 찾아보니, 마크다운 텍스트를 추출해주는 내장 메서드 getMarkdown()가 있었다.

const editorRef = useRef();
editorRef.current.getInstance().getMarkdown()


콘솔에서 이와 같이 확인을 할 수 있었다.

마크다운 데이터를 어떻게 화면에 렌더링할까?

마크다운을 추출한 뒤, 이를 그대로 서버에게 post 요청으로 보낼 것이다(이건 백엔드 분들과 이야기를 해 보아야 하겠지..)

이후에는 전체 게시물을 서버에서 받아올 것이다. 하지만 저 마크다운 문법을 화면 그대로 렌더링할 수는 없었다. 이를 변환해주는 도구가 없나 찾아보다가 알아낸 것이 마크다운 파서였다. 토스트 에디터 api 공식 문서를 보니, 마크다운 파서를 사용해 미리보기 뷰를 지원해준다는 것을 알아내서, 관련된 모듈을 찾아보게 되었다.

내가 알아낸 모듈은 markdown-it 이다.

//설치
npm i markdown-it

//모듈 불러오기
import markdownIt from "markdown-it";

사용 방법은 다음과 같다.

<div dangerouslySetInnerHTML={{
          __html: markdownIt().render(text),
        }}
      ></div>

dangerouslySetInnerHTML는 DOM의 innerHTML메서드를 이용한 속성인데, 이를 개발에 사용할 땐, 악성 스크립트를 의도적으로 심는 경우를 대처하지 못한다는 문제점이 있다.

따라서 악의적인 스크립트를 좀 더 안전한 형태의 코드로 바꿔줘야 한다. 이를 sanitizing이라고 한다. 예를 들면 태그의 시작은 < 로 나타내는데 > 와 같은 식으로 바꿔 표현하는 걸 말한다.

not to use dangerouslySetInnerHTML라고 검색했고, 관련 모듈도 찾아보았다.
DOMPurify 모듈을 쓸 수 있다.

npm i dompurify
import DOMPurify from 'dompurify';

사용법

import dompurify from 'dompurify';

function MyComponent() {
  const title = response.from.backend.title;
  const sanitizer = dompurify.sanitize;
  
  return <div dangerouslySetInnerHTML={{__html: sanitizer(title)}} />; // Good
}
let clean = DOMPurify.sanitize(dirty);
//dirty에다가 내가 받은 스트링을 넣으면 clean 변수에 안전한 HTML표현식이 할당됨.
(위는 기본 옵션으로 HTML, SVG, 수식 표현 위한 MAthML 지원함.)

//사용 예
DOMPurify.sanitize('<img src=x onerror=alert(1)//>'); // becomes <img src="x">

이를 바탕으로 연습해 본 최종 코드!

import "./App.css";
import "@toast-ui/editor/dist/toastui-editor.css";
import { Editor } from "@toast-ui/react-editor";
import markdownIt from "markdown-it";
import DOMPurify from "dompurify";
import { useRef, useState } from "react";

function App() {
  const editorRef = useRef();
  const [text, setText] = useState("");
  const sanitizer = DOMPurify.sanitize;
  const handleClick = () => {
    setText(editorRef.current.getInstance().getMarkdown());
    console.log("작동함", text);
    console.log(markdownIt().render(text), "태그화");
  };
  const handleFocus = () => {
    console.log("focus!!");
    editorRef.current.getRootElement().classList.add("my-editor-root");
  };
  return (
    <div className="App">
      <Editor
        initialValue="hello react editor world!"
        previewStyle="vertical"
        height="500px"
        minHeight="200px"
        initialEditType="markdown"
        useCommandShortcut={true}
        ref={editorRef}
        onFocus={handleFocus}
      />
      <button onClick={handleClick}>글쓰기</button>
      <div
        dangerouslySetInnerHTML={{
          __html: sanitizer(markdownIt().render(text)),
        }}
      ></div>
    </div>
  );
}

export default App;

화면에서 글쓰기가 되는지 확인해보자.


위는 getMarkdown() 함수를 실행한 결과값, 아래는 markdownIt().render로 렌더시킨 값이다.
일단 서버 요청 코드는 작성하지 못했지만 useState를 이용해 텍스트값을 상태로 두고 화면에 나타내보았다. 코드 스니펫의 경우는 pre 태그를 사용했기에 App.css 파일에 관련 속성을 설정했다.

/* 코드 스니펫 영역 */
pre {
  padding: 0.5rem;
  background-color: rgb(233, 233, 233);
  font-size: 10px;
  border-radius: 5px;
}
/*스니펫 영약 내 코드*/
code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
    monospace;
}

작성화면

글쓴 결과 화면

한계

안타깝게도 체크박스는 전환이 안된다ㅠㅠ

이미지 첨부 후 서버 전송?

https://gareen.tistory.com/49

서버에서 받은 이미지와 글 같이 렌더링?

getMarkdown()덕분에 이미지도 문제없이 받아와진다. 서버 전송 방법만 신경쓰면 될것같다.

profile
몫을 다하는 사람

0개의 댓글

관련 채용 정보