본문 바로가기
개발/React

리액트 - Toast UI 에디터 저장하고 초기화하기 (Save and Initialize Toast UI Editor)

by 피로물든딸기 2023. 7. 30.
반응형

리액트 전체 링크

 

참고

- 로컬 스토리지 사용 방법과 세션 스토리지 비교

- https://nhn.github.io/tui.editor/latest/tutorial-example16-i18n

 

- Toast UI로 에디터 만들기

- 글자색 변경 플러그인 추가하기

- 테이블 병합 플러그인 추가하기

- 에디터 저장하고 초기화하기

- Toast UI 에디터로 깃허브 마크다운 저장하기

- Toast UI 에디터로 이미지를 포함한 깃허브 마크다운 저장하기

- Socket.IO로 Toast UI Editor 동시 편집하기

 

토스트 UI 에디터에서 저장하기 버튼을 누르면 편집한 내용이 저장되도록 해보자.


initialValue 옵션

 

initalValue에 값을 넣어주면 해당 내용으로 에디터가 시작된다.

let initContents = "Test!!";

...

  <Editor
    initialValue={initContents}
    height="400px"
    placeholder="Please Enter Text."
    ...
  />


useRef로 콘텐츠 내용 가져오기

 

useRef를 이용하면 현재 에디터의 내용을 HTML이나 Markdown으로 가져올 수 있다.

 

먼저 ref를 추가하자.

import React, { useRef } from "react";

const editorRef = useRef(null);

 

에디터의 ref에 useRef로 만든 editorRef를 추가한다.

  <Editor
    ref={editorRef}
    initialValue={initContents}

 

아래 함수를 추가한다. 

editorRef의 current Instance에서 markdown과 HTML을 얻을 수 있다.

  const handleSave = () => {
    let markDownContent = editorRef.current.getInstance().getMarkdown();
    let htmlContent = editorRef.current.getInstance().getHTML();
    
    console.log(markDownContent);
    console.log("================================================");
    console.log(htmlContent);
  };

 

버튼을 추가해서 위의 함수를 호출해 보자.

  <Button
    variant="outlined"
    color="primary"
    sx={{ m: 1 }}
    onClick={handleSave}
  >
    저장하기
  </Button>

 

콘솔에 정상적으로 HTML과 markdown이 출력되었다.

 

위에서 편집한 내용markdown은 다음과 같다.

# 제목

***~~<span style="color: #EE2323">내용 1</span>~~***

* [x] 체크 
* [ ] 체크 해제

| 테이블 1 | 테이블 2 |
| ----- | ----- |
| 11111 | 22222 |
| 11111 | 2222 |

 

HTML은 다음과 같다.

<h1>제목</h1><p><strong><em><del><span style="color: #EE2323">내용 1</span></del></em></strong></p><p><br></p><ul><li class="task-list-item checked" data-task="true" data-task-checked="true"><p>체크 </p></li><li class="task-list-item" data-task="true"><p>체크 해제</p></li></ul><table><thead><tr><th><p>테이블 1</p></th><th><p>테이블 2</p></th></tr></thead><tbody><tr><td><p>11111</p></td><td><p>22222</p></td></tr><tr><td><p>11111</p></td><td><p>2222</p></td></tr></tbody></table>

 

initContents에 markdown을 넣어보자.

  let initContents = `# 제목

  ***~~<span style="color: #EE2323">내용 1</span>~~***
  
  * [x] 체크 
  * [ ] 체크 해제
  
  | 테이블 1 | 테이블 2 |
  | ----- | ----- |
  | 11111 | 22222 |
  | 11111 | 2222 |`;

 

다시 에디터를 실행하면 이전에 저장한 내용이 출력된다.

 

HTML의 경우도 테스트해 보자.

 let initContents = `<h1>제목</h1><p><strong><em><del><span style="color: #EE2323">내용 1</span></del></em></strong></p><p><br></p><ul><li class="task-list-item checked" data-task="true" data-task-checked="true"><p>체크 </p></li><li class="task-list-item" data-task="true"><p>체크 해제</p></li></ul><table><thead><tr><th><p>테이블 1</p></th><th><p>테이블 2</p></th></tr></thead><tbody><tr><td><p>11111</p></td><td><p>22222</p></td></tr><tr><td><p>11111</p></td><td><p>2222</p></td></tr></tbody></table>`;

 

HTML은 markdown으로 변경하면 테이블과 설정한 내용이 깨지는 버그가 발생한다.

 

따라서 내용을 저장할 땐 markdown으로 저장해야 한다.

 

전체 코드는 다음과 같다.

import React, { useEffect, useRef, useState } from "react";

import Box from "@mui/material/Box";
import Button from "@mui/material/Button";

// Toast UI Editor
import "@toast-ui/editor/dist/toastui-editor.css";
import { Editor } from "@toast-ui/react-editor";

// Dark Theme 적용
// import '@toast-ui/editor/dist/toastui-editor.css';
// import '@toast-ui/editor/dist/theme/toastui-editor-dark.css';

// Color Syntax Plugin
import "tui-color-picker/dist/tui-color-picker.css";
import "@toast-ui/editor-plugin-color-syntax/dist/toastui-editor-plugin-color-syntax.css";
import colorSyntax from "@toast-ui/editor-plugin-color-syntax";

// Table Merged Cell Plugin
import "@toast-ui/editor-plugin-table-merged-cell/dist/toastui-editor-plugin-table-merged-cell.css";
import tableMergedCell from "@toast-ui/editor-plugin-table-merged-cell";

const colorSyntaxOptions = {
  preset: [
    "#333333", "#666666", "#FFFFFF", "#EE2323", "#F89009", "#009A87", "#006DD7", "#8A3DB6",
    "#781B33", "#5733B1", "#953B34", "#FFC1C8", "#FFC9AF", "#9FEEC3", "#99CEFA", "#C1BEF9",
  ],
};

const App = () => {
  const editorRef = useRef(null);

  let initContents = `# 제목

  ***~~<span style="color: #EE2323">내용 1</span>~~***
  
  * [x] 체크 
  * [ ] 체크 해제
  
  | 테이블 1 | 테이블 2 |
  | ----- | ----- |
  | 11111 | 22222 |
  | 11111 | 2222 |`;
  
  const handleSave = () => {
    let markDownContent = editorRef.current.getInstance().getMarkdown();
    let htmlContent = editorRef.current.getInstance().getHTML();
    
    console.log(markDownContent);
    console.log("================================================");
    console.log(htmlContent);
  };

  return (
    <div>
      <Box sx={{ m: 2 }}>
        <h1>Toast UI Editor</h1>
        <Button
          variant="outlined"
          color="primary"
          sx={{ m: 1 }}
          onClick={handleSave}
        >
          저장하기
        </Button>
        <Editor
          ref={editorRef}
          initialValue={initContents}
          height="400px"
          placeholder="Please Enter Text."
          previewStyle="tab" // or vertical
          initialEditType="wysiwyg" // or markdown
          // hideModeSwitch={true} // 하단 숨기기
          toolbarItems={[
            // 툴바 옵션 설정
            ["heading", "bold", "italic", "strike"],
            ["hr", "quote"],
            ["ul", "ol", "task", "indent", "outdent"],
            ["table", /* "image", */ "link"],
            ["code", "codeblock"],
          ]}
          //theme="dark"
          //useCommandShortcut={false} // 키보드 입력 컨트롤 방지 ex ctrl z 등
          usageStatistics={false} // 통계 수집 거부
          plugins={[[colorSyntax, colorSyntaxOptions], tableMergedCell]}
        />
      </Box>
    </div>
  );
};

export default App;

로컬 스토리지를 이용하여 저장하기 구현

 

이제 로컬 스토리지를 이용하여 편집기의 내용을 저장해 보자.

 

참고로 useEffect를 이용하여 아래와 같이 initalValue를 불러와도 정상적으로 콘텐츠가 초기화되지 않는다.

  const [initContents, setInitContents] = useState("");
  
  ...

  useEffect(() => {
    let item = localStorage.getItem(CONTENT_KEY);
    if(item) {
      setInitContents(item);
    }   
  }, []);
  
  <Editor
    ref={editorRef}
    initialValue={initContents}
    height="400px"
    ...
  />

 

useEffect에는 setMarkdown을 이용해야 한다.

editorRef.current.getInstance().setMarkdown(item);

 

먼저 handleSave에 localStorage 저장 코드를 추가하자.

  const CONTENT_KEY = "CONTENT_KEY";
  
  const handleSave = () => {
    let markDownContent = editorRef.current.getInstance().getMarkdown();
    //let htmlContent = editorRef.current.getInstance().getHTML();
    
    localStorage.setItem(CONTENT_KEY, markDownContent);
  };

 

useEffect에서 setMarkdown 메서드를 이용해 localStorage에 저장해둔 내용을 설정한다.

  useEffect(() => {
    let item = localStorage.getItem(CONTENT_KEY);
    if(item) {
      editorRef.current.getInstance().setMarkdown(item);
    }   
  }, []);

 

Editor의 initalValue는 더 이상 필요가 없다.

  <Editor
    ref={editorRef}
    // initialValue={initContents}

 

정상적으로 편집한 내용이 잘 저장되는지 확인해 보자.

 

전체 코드는 다음과 같다.

import React, { useEffect, useRef, useState } from "react";

import Box from "@mui/material/Box";
import Button from "@mui/material/Button";

// Toast UI Editor
import "@toast-ui/editor/dist/toastui-editor.css";
import { Editor } from "@toast-ui/react-editor";

// Dark Theme 적용
// import '@toast-ui/editor/dist/toastui-editor.css';
// import '@toast-ui/editor/dist/theme/toastui-editor-dark.css';

// Color Syntax Plugin
import "tui-color-picker/dist/tui-color-picker.css";
import "@toast-ui/editor-plugin-color-syntax/dist/toastui-editor-plugin-color-syntax.css";
import colorSyntax from "@toast-ui/editor-plugin-color-syntax";

// Table Merged Cell Plugin
import "@toast-ui/editor-plugin-table-merged-cell/dist/toastui-editor-plugin-table-merged-cell.css";
import tableMergedCell from "@toast-ui/editor-plugin-table-merged-cell";

const colorSyntaxOptions = {
  preset: [
    "#333333", "#666666", "#FFFFFF", "#EE2323", "#F89009", "#009A87", "#006DD7", "#8A3DB6",
    "#781B33", "#5733B1", "#953B34", "#FFC1C8", "#FFC9AF", "#9FEEC3", "#99CEFA", "#C1BEF9",
  ],
};

const CONTENT_KEY = "CONTENT_KEY";

const App = () => {
  const editorRef = useRef(null);

  const handleSave = () => {
    let markDownContent = editorRef.current.getInstance().getMarkdown();
    //let htmlContent = editorRef.current.getInstance().getHTML();

    localStorage.setItem(CONTENT_KEY, markDownContent);
  };

  useEffect(() => {
    let item = localStorage.getItem(CONTENT_KEY);
    if(item) {
      editorRef.current.getInstance().setMarkdown(item);
    }   
  }, []);

  return (
    <div>
      <Box sx={{ m: 2 }}>
        <h1>Toast UI Editor</h1>
        <Button
          variant="outlined"
          color="primary"
          sx={{ m: 1 }}
          onClick={handleSave}
        >
          저장하기
        </Button>
        <Editor
          ref={editorRef}
          // initialValue={initContents}
          height="400px"
          placeholder="Please Enter Text."
          previewStyle="tab" // or vertical
          initialEditType="wysiwyg" // or markdown
          // hideModeSwitch={true} // 하단 숨기기
          toolbarItems={[
            // 툴바 옵션 설정
            ["heading", "bold", "italic", "strike"],
            ["hr", "quote"],
            ["ul", "ol", "task", "indent", "outdent"],
            ["table", /* "image", */ "link"],
            ["code", "codeblock"],
          ]}
          //theme="dark"
          //useCommandShortcut={false} // 키보드 입력 컨트롤 방지 ex ctrl z 등
          usageStatistics={false} // 통계 수집 거부
          plugins={[[colorSyntax, colorSyntaxOptions], tableMergedCell]}
        />
      </Box>
    </div>
  );
};

export default App;
반응형

댓글