본문 바로가기

JavaScript/React

[React] 웹 에디터 React-Quill 사용하기 (1)

반응형

게시글 작성 기능을 구현할 때 그냥 글 작성 기능만 만들면 무언가 심심해 보인다. 폰트 색 변경, 크기 조정, 이미지 추가 등의 기능을 사용할 수 있는 웹 에디터 기능이 있다면 글 작성 페이지의 퀄리티를 높일 수 있을 것이다.

 

여러 무료 웹 에디터 라이브러리 중 React-Quill을 사용하여 웹 에디터 기능을 구현해 보자

 

 

 

1. React-Quill 적용

npm install react-quill

 

 

// reactEditor.tsx

'use client';

import ReactQuill from "react-quill";
import 'react-quill/dist/quill.snow.css';

const ReactEditor = () => {

    return (
        <div>
            <ReactQuill theme="snow"/>
        </div>
    )
}

export default ReactEditor;

 

 

위와 같이 설치 후 코드를 작성하면 아래와 같이 출력이 될 것이다.

theme 설정을 안 하면 css가 망가진 상태로 출력이 되니 꼭 적용을 하자

 

위위 이미지는 제일 기본적인 형태이며, React-Quill은 개발자가 원하는 대로 커스텀을 할 수 있기에 커스텀을 통해 더 많은 기능을 담고 있는 에디터를 만들어 보겠다.

 

728x90

 

 

 

 

 

 

 

 

 

2. React-Quill 커스텀

우선 모듈 설정을 해보겠다. 

아래와 같이 원하는 기능이 있는 모듈을 생성한 뒤 React Quill에다 적용만 해주면 끝이다.

크기 또한 Style을 통해 설정이 가능하다. 

// reactEditor.tsx

'use client';

import { useMemo } from "react";
import ReactQuill from "react-quill";
import 'react-quill/dist/quill.snow.css';

const ReactEditor = () => {

    const modules:{} = useMemo(() => ({
        toolbar: {
            container: [
                ["image"],
                [{ header: [1, 2, 3, 4, 5, false] }],
                ["bold", "underline"]
            ]
        },
    }), []);

    return (
        <div>
            <ReactQuill theme="snow" modules={modules} style={{height: "300px", width: "500px"}}/>
        </div>
    )
}

export default ReactEditor;

 

 

아래와 같이 출력이 된다. 이미지 추가 기능 또한 잘 동작한다.

 

 

 

위에 작성된 코드처럼 원하는 모듈을 추가함으로써 구현이 가능하기도 하나 나는 아래와 같이 여러 기능을 담은 모듈 툴바를 컴포넌트 형태로 생성한 뒤 가져다가 사용하였다.

좀 더 디테일하게 스타일링을 할 수 있고 이 방법이 내게 익숙하기에 아래와 같이 작성하였다.

// ReactModule.tsx

const ReactModule = () => {

    return (
        <>
            <div className="ql-formats">
                <select className="ql-header" defaultValue="7">
                    <option value="1">Header 1</option>
                    <option value="2">Header 2</option>
                    <option value="3">Header 3</option>
                    <option value="4">Header 4</option>
                    <option value="5">Header 5</option>
                    <option value="6">Header 6</option>
                    <option value="7">Normal</option>
                </select>
                <select className="ql-size" defaultValue="medium">
                    <option value="small">Small</option>
                    <option value="medium">Medium</option>
                    <option value="large">Large</option>
                    <option value="huge">Huge</option>
                </select>
                <select className="ql-font" defaultValue="sans-serif" />
            </div>
            <div className="ql-formats">
                <button className="ql-bold" />
                <button className="ql-italic" />
                <button className="ql-underline" />
                <button className="ql-strike" />
                <button className="ql-blockquote" />
            </div>

            <div className="ql-formats">
                <button className="ql-list" value="ordered" />
                <button className="ql-list" value="bullet" />
                <button className="ql-indent" value="-1" />
                <button className="ql-indent" value="+1" />
            </div>
            <div className="ql-formats">
                <select className="ql-color" />
                <select className="ql-background" />
                <select className="ql-align" />
            </div>
            <div className="ql-formats">
                <button className="ql-code-block" />
                <button className="ql-link" />
                <button className="ql-image" />
            </div>
        </>
    )
}

export default ReactModule;

 

// reactEditor.tsx

'use client';

import { useMemo } from "react";
import ReactQuill from "react-quill";
import 'react-quill/dist/quill.snow.css';

import ReactModule from "./reactModule";

const ReactEditor = () => {

    const formats:string[] = [
        "header", "size", "font",
        "bold", "italic", "underline", "strike", "blockquote",
        "list", "bullet", "indent", "link", "image",
        "color", "background", "align",
        "script", "code-block", "clean"
    ];

    const modules:{} = useMemo(() => ({
        toolbar: {
            container: "#toolBar"
        },
    }), []);

    return (
        <div>
            <div id="toolBar">
                <ReactModule />
            </div>
            <ReactQuill theme="snow" modules={modules} formats={formats} 
            		style={{height: "300px", width: "850px"}}/>
        </div>
    )
}

export default ReactEditor;

 

 

위와 같이 작성 시 아래와 같은 화면이 출력이 된다.

기존 기능에서 폰트 크기, 색, 스타일, 배경, 들여쓰기, 코드블럭 등등을 추가하였다.

툴바 메뉴 순서 또한 위에서 자신이 원하는 대로 수정을 하여 입맛대로 커스텀해주면 된다.

 

 

 

다음으로 styled-component를 활용하여 에디터를 디자인해보겠다. 참고용으로 간단하게 작성하였다.

// reactEditor.tsx

'use client';

import { useMemo, useState } from "react";
import ReactQuill from "react-quill";
import 'react-quill/dist/quill.snow.css';

import ReactModule from "./reactModule";
import styled from "styled-components";

const CustomQuillEditorView = styled.div`

  #toolBar {
    box-sizing: border-box;
    height: 40px;
    width: 100%;
    border: 2px solid black;
    border-radius: 10px;
    color: black;
    font-size: 32px;
    
    .ql-formats {
      display: inline-block;
      position: relative;
      top: -10px;
    
      .image-btn {
        font-size: 18px;
        cursor: pointer;
        
        .icon-custom {
          margin-right: 5px;
          font-size: 24px;
        }
      }
    }
  }

  #quillContent {
    border: 2px solid black;
    border-radius: 10px;
    background-color: grey;
    
    .ql-container {
      box-sizing: border-box;
      height: 250px;
      width: 850px;
      padding: 5px 10px;
      border: none;
      
      .ql-editor {

        &::-webkit-scrollbar {
          width: 5px;
        }

        &::-webkit-scrollbar-thumb {
          background: gray; /* 스크롤바의 색상 */
          border-radius: 15px;
        }

        &::-webkit-scrollbar-track {
          background: rgba(200, 200, 200, .1);
        }
      }
    }
  }

`;

const ReactEditor = () => {

    const [content, setContent] = useState<string>("");

    const formats:string[] = [
        "header", "size", "font",
        "bold", "italic", "underline", "strike", "blockquote",
        "list", "bullet", "indent", "link", "image",
        "color", "background", "align",
        "script", "code-block", "clean"
    ];

    const modules:{} = useMemo(() => ({
        toolbar: {
            container: "#toolBar"
        },
    }), []);

    return (
        <CustomQuillEditorView>
            <div id="toolBar">
                <ReactModule />
            </div>
            <ReactQuill theme="snow" modules={modules} formats={formats} id="quillContent"
                        value={content} onChange={setContent}/>
        </CustomQuillEditorView>
    )
}

export default ReactEditor;

 

 

위와 같이 작성 시 아래와 같이 출력이 된다.

 

 

위 코드들을 참고하여 자신이 원하는 대로 커스텀해주면 되겠다.

 

반응형

 

 

 

 

 

 

 

 

 

 

3. React-Quill 출력 (feat. dompurify)

에디터를 통해 작성한 글은 아래와 같이 html 태그 형태로 생성이 된다.

 

그렇기에 만약 에디터를 통해 글을 작성 후 저장을 한 뒤 데이터를 불러오면 아래와 같이 <p>content</p> 형태로 출력이 된다. 

 

 

이를 해결하고자 dangerouselySetInnerHTML을 사용해주어야 한다. 하지만 이를 사용하면 XSS 공격으로 인해 악의적인 용도의 악성 스크립트 코드가 삽입이 될 수도 있다. 그래서 dompurify를 사용한 Santize를 통해 삽입되는 텍스트를 한번 필터링 해주어야 한다.

 

우선 dompurify를 설치해주자

npm install dompurify @types/dompurify

 

 

그리고 아래와 같이 코드를 작성해 주면 된다.

'use client';

import { useMemo, useState } from "react";
import ReactQuill from "react-quill";
import 'react-quill/dist/quill.snow.css';

import ReactModule from "./reactModule";
import styled from "styled-components";
import dompurify from "dompurify";

... 

const ReactEditor = () => {

    const [content, setContent] = useState<string>("");
    
    // 스크립트를 활용하여 javascript와 HTML로 악성 코드를 웹 브라우저에 심어,
    // 사용자 접속시 그 악성코드가 실행되는 것을 XSS, 보안을 위해 sanitize 추가
    const sanitizer = dompurify.sanitize;

    ...

    return (
        <CustomQuillEditorView>
            <div id="toolBar">
                <ReactModule />
            </div>
            <ReactQuill theme="snow" modules={modules} formats={formats} id="quillContent"
                        value={content} onChange={setContent}/>
            
            <div id="content">
                <div>
                    {content}
                </div>
                // 출력이 되는 부분
                <div dangerouslySetInnerHTML={{ __html : sanitizer(`${content}`) }} />
            </div>
        </CustomQuillEditorView>
    )
}

export default ReactEditor;

 

 

아래와 같이 해결이 된 형태로 출력이 될 것이다.

왼쪽은 그냥 출력된 것, 오른쪽이 dangerouselySetInnerHTML를 사용하여 html 태그들이 적용되어 출력된 것이다.



 

 

 

더 상세한 기능과 적용을 알고 싶다면 공식 홈페이지에서 확인해주면 되겠다.

https://quilljs.com/docs/quickstart

 

Quill - Your powerful rich text editor

Built for Developers Granular access to the editor's content, changes and events through a simple API. Works consistently and deterministically with JSON as both input and output.

quilljs.com

 

 

 

관련 포스팅

 

2024.06.22 - [React] - [React] 웹 에디터 React-Quill 사용하기 (2)

 

[React] 웹 에디터 React-Quill 사용하기 (2)

관련 포스팅 2024.06.16 - [React] - [React] 웹 에디터 React-Quill 사용하기 (1) [React] 웹 에디터 React-Quill 사용하기 (1)게시글 작성 기능을 구현할 때 그냥 글 작성 기능만 만들면 무언가 심심해 보인다. 폰

rlawo32.tistory.com

 

 

 

 

 

 

 

 

 

 

728x90
반응형