[Editor] Quill 에디터 적용하기
본문 바로가기
더 알아보기/기능

[Editor] Quill 에디터 적용하기

by 은돌1113 2022. 3. 23.
 

[React] Next.js에서 Quill 에디터 사용하기

이 글에는 Next.js에서 react-quill의 사용 없이 Quill 에디터를 사용하기 위한 코드를 제공합니다. 코드의 부족한 점과 문의 사항은 댓글 주시면 답변 및 수정하도록 하겠습니다. 샘플 프로젝트 다운

constructionsite.tistory.com


위 블로그를 참고하여 Next.js에 Quill을 적용하여 Editor를 만들었다.

 

예제에서 .css 부분은 styled-components로 대체하였다.

(특별한 이유가 있는 건 아니고 그냥 내가 styled-components를 사용하는 게 편해서 바꿨다.)

// pages/editor_test.js

import Head from "next/head";
import { useState } from "react";
import QuillEditor from "../elements/nav/Notice/QuillEditor";
import styled from "styled-components";

const Test = () => {

  const [body, setBody] = useState(""); // Quill 에디터의 innerHTML을 담는 state
  const [mountBody, setMountBody] = useState(false); // 리렌더링 용도 state

  return (
    <Style>
      <Head>
        {/* 관련된 리소스 로드 */}
        <link
          href="//cdn.jsdelivr.net/npm/katex@0.13.3/dist/katex.min.css"
          rel="stylesheet"
        />
        <script src="//cdn.jsdelivr.net/npm/katex@0.13.3/dist/katex.min.js"></script>
        <script src="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.7.2/build/highlight.min.js"></script>
        <script src="//cdn.quilljs.com/1.3.6/quill.min.js"></script>
        <link
          rel="stylesheet"
          href="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.7.2/build/styles/default.min.css"
        />
        <link rel="stylesheet" href="//cdn.quilljs.com/1.3.6/quill.snow.css" />
      </Head>

      <div style={{ width: "50%", marginTop: "40px" }}>
        <QuillEditor
          body={body}
          handleQuillChange={setBody}
          mountBody={mountBody}
        />
      </div>
      <hr />
      <div style={{ width: "80%" }}>
        <p>body state 미리보기</p>
        {body}
      </div>
    </Style>
  );
};

const Style = styled.div`
  .container {
    min-height: 100vh;
    padding: 0 0.5rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
  }

  .title {
    margin: 0;
    line-height: 1.15;
    font-size: 4rem;
  }

  .description {
    line-height: 1.5;
    font-size: 1.5rem;
  }
`;

export default Test;

아래 파일은 예제와 동일하다.

// elements/nav/notice/auillEditor.js

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

const QuillEditor = ({ body, handleQuillChange, mountBody }) => {
  const quillElement = useRef();
  const quillInstance = useRef();

  const [isError, setIsError] = useState(false);
  const [isLoaded, setIsLoaded] = useState(false);

  useEffect(() => {
    if (isLoaded) {
      /* isLoaded가 true인 상태에서 rerenderBody를 통해 body 적용시 Quill 초기화 없이
               innerHTML만 body로 바꿉니다. 이 조건이 없을 시 툴바가 중복되어 여러 개 나타나게
               됩니다. */
      const quill = quillInstance.current;
      quill.root.innerHTML = body;
      return;
    }
    if (quillElement.current && window.Quill) {
      /* isLoaded가 false일 때는 Quill을 초기화합니다. */

      /* Quill 옵션을 원하는 대로 수정하세요. */
      const toolbarOptions = {
        container: [
          [{ size: ["small", false, "large", "huge"] }], // custom dropdown
          [{ header: [1, 2, 3, 4, 5, 6, false] }],
          [{ align: [] }],
          ["bold", "italic", "underline", "strike"], // toggled buttons
          [{ color: [] }, { background: [] }], // dropdown with defaults from theme
          [{ header: 1 }, { header: 2 }], // custom button values
          [{ list: "ordered" }, { list: "bullet" }],
          [{ script: "sub" }, { script: "super" }], // superscript/subscript
          [{ indent: "-1" }, { indent: "+1" }], // outdent/indent
          [{ direction: "rtl" }], // text direction
          ["clean"], // remove formatting button
          ["blockquote", "link", "code-block", "formula", "image", "video"], // media
        ],
      };

      quillInstance.current = new window.Quill(quillElement.current, {
        modules: {
          history: {
            delay: 2000,
            maxStack: 500,
            userOnly: true,
          },
          syntax: true,
          toolbar: toolbarOptions,
        },
        placeholder: "본문 입력",
        theme: "snow",
      });

      const quill = quillInstance.current;

      quill.root.setAttribute("spellcheck", "false");

      // 초기 body state 적용
      quill.root.innerHTML = body;

      /* quill에서 text-change 이벤트 발생시에 setBody(innerHTML)을 통해 body를 업데이트합니다.
               body가 업데이트되어도 useEffect 발생 조건 인자([isError, mountBody])에 body가 없으므로
               QuillEditor 컴포넌트는 다시 렌더링되지 않습니다. 이는 입력 중 커서가 맨 앞으로 이동하는
               문제를 방지합니다. 대신 외부에서 body가 수정되어도 rerenderBody가 호출되지 않으면 변경된
               body가 적용되지 않습니다. */
      quill.on("text-change", () => {
        handleQuillChange(quill.root.innerHTML);
      });

      setIsLoaded(true);
    } else {
      /* quill.min.js가 로드되어 있지 않아 window.Quill이 undefined이면 isError가
               계속 변경되면서 재시도합니다. */
      setIsError((prevIsError) => !prevIsError);
    }
  }, [isError, mountBody]);

  return <div ref={quillElement}></div>;
};

export default QuillEditor;

 

여기까지 진행하면 아래와 같은 화면이 만들어 진다.

'더 알아보기 > 기능' 카테고리의 다른 글

[Next.js] 이미지 미리보기  (0) 2022.03.24
[Editor] String 형태의 HTML 렌더링 하기  (2) 2022.03.23
Zustand 사용하기  (0) 2022.03.21
[Recoil] 공식문서로 공부하기  (0) 2022.03.17
[Recoil] 예습  (0) 2022.03.14

댓글