[TIL] 10월 13일 WebEditor

기록하며 공부하자·2021년 10월 18일
0

네이버 블로그나 벨로그등을 보면 게시글 입력시 다양한 기능(글씨크기, 정렬, 글씨색 등등..)이 추가되어 있는 WebEditor가 있다.

네이버블로그

velog

물론 네이버블로그와, velog는 자체개발한 내용이겠지만 웹에디터를 제공해주는 라이브러리가 있다.
html태그인 textarea를 작성하는것 보다 훨씬더 많은 내용을 넣어줄수 있다.

ReactQuil

ReactQuil을 사용하여 WebEditor를 구현할수 있다.

ReactQuil은 npm으로 설치할수 있다.

https://www.npmjs.com/package/react-quill

설치가 완료되었다면 이제 import를 한다.

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

import를 했을시 css 파일까지 같이 import 해준다.
이상태까지 하고 이렇게 선언해서 사용한다면 에러가 발생하게 된다.

documet is not defined 라고 나오는데 Next.js 사용하면 정상적으로 나오는 에러이며 웹페이지를 렌더링하는 시점에서는 window 또는 document object를 선언하기 전이기 때문에 발생하는 에러가 발생하게 된다.

이방식을 해결하기 위해선 dynamic import 방식을 사용해야 한다.

dynamic import

import dynamic from 'next/dynamic';

const ReactQuill = dynamic( () => import('react-quill'), {
    ssr : false
})

dynamic 은 해당 모듈을 호출하는 시점을 document에 대한 정보가 선언된 후의 시점으로 옮겨서 호출할수 있게 도와주는것이다.

이렇게 선언후 다시 확인해보면 아래와같이 잘 적용된다.

이제 입력을 한다면 조금 이상하게 입력이 된다.
onChange를 걸고 확인해 보면 아래화면처럼 된다.

내용만 있는것이 아니래 html 태그까지 같이 들어가 있는것을 확인할수 있다.
이러한 내용을 입력받은 화면에서 보여주게 된다면 태그내용이 전체 들어가게 되는데 React 프로젝트에서는 보안이슈로 인해 html 태그를 직접 삽입 할 수 없게 설정되어 있다.

태그를 사용하려면 아래 태그를 추가해준다.

<div dangerouslySetInnerHTML={{ __html :  HTML 태그 추가  }} />

ReactQuill Custom

기본 ReactQuill은 기능이 많이 없다.

글씨색 변경도 없고 조금 휑한느낌이다.
modules라는 속성으로 내가 원하는 기능을 추가할수 있다.

modules 예시

const modules = {
    toolbar: {
        container: [
          [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
          [{ 'font': [] }],
          [{ 'align': [] }],
          ['bold', 'italic', 'underline', 'strike', 'blockquote'],
          [{ 'list': 'ordered' }, { 'list': 'bullet' }, 'link'],
          [{ 'color': ['#000000', '#e60000', '#ff9900', '#ffff00', '#008a00', '#0066cc', '#9933ff', '#ffffff', '#facccc', '#ffebcc', '#ffffcc', '#cce8cc', '#cce0f5', '#ebd6ff', '#bbbbbb', '#f06666', '#ffc266', '#ffff66', '#66b966', '#66a3e0', '#c285ff', '#888888', '#a10000', '#b26b00', '#b2b200', '#006100', '#0047b2', '#6b24b2', '#444444', '#5c0000', '#663d00', '#666600', '#003700', '#002966', '#3d1466', 'custom-color'] }, { 'background': [] }],
          ['image', 'video'],
          ['clean']  
        ],
    }
}

ReactQuill 적용 전체코드(작성)

import { useMutation, gql } from "@apollo/client";
import { useRouter } from "next/router";
import { useForm } from "react-hook-form";
// import ReactQuil from "react-quill"; 프론트엔드 서버에서 그릴때, document가 없어서 문제가 됨
import dynamic from "next/dynamic";
const ReactQuil = dynamic(() => import("react-quill"), { ssr: false });
import "react-quill/dist/quill.snow.css";
const CREATE_BOARD = gql`
  mutation createBoard($createBoardInput: CreateBoardInput!) {
    createBoard(createBoardInput: $createBoardInput) {
      _id
    }
  }
`;
export default function WebEditorPage() {
  const [createBoard] = useMutation(CREATE_BOARD);
  const router = useRouter();
  //   const onClickMyButton2 = () => async() => {
  //   }
  async function onClickMyButton(data) {
    try {
      const result = await createBoard({
        variables: {
          createBoardInput: {
            writer: data.writer,
            password: data.password,
            title: data.title,
            contents: data.contents,
          },
        },
      });
      router.push(`/28-02-web-editor-detail/${result.data.createBoard._id}`);
    } catch (error) {
      console.log(error);
    }
  }
  const { handleSubmit, register, setValue, trigger } = useForm({
    mode: "onChange",
  });
  function onChangeMyEditor(value) {
    // register로 등록하지 않고, 강제로 값을 넣어주는 기능
    setValue("contents", value === "<p><br></P" ? "" : value);
    console.log(value);
    // onchange 됐는지 react-hook-form에 알려주는 기능
    trigger("contents");
  }
  return (
    <form onSubmit={handleSubmit(onClickMyButton)}>
      작성자 : <input type="text" {...register("writer")} />
      <br />
      비밀번호 : <input type="password" {...register("password")} />
      <br />
      제목 : <input type="text" {...register("title")} />
      <br />
      내용 : <ReactQuil onChange={onChangeMyEditor} />
      <br />
      <button type="submit">등록하기</button>
    </form>
  );
}

ReactQuill 적용 전체코드(보여지는 페이지)

import { gql, useQuery } from "@apollo/client";
import { useRouter } from "next/router";
import Dompurify from "dompurify";
const FETCH_BOARD = gql`
  query fetchBoard($boardId: ID!) {
    fetchBoard(boardId: $boardId) {
      writer
      title
      contents
    }
  }
`;
export default function WebEditorDetailPage() {
  const router = useRouter();
  const { data } = useQuery(FETCH_BOARD, {
    variables: {
      boardId: router.query.id,
    },
  });
  console.log(process.browser);
  console.log(data);
  return (
    <>
      <div>작성자 : {data?.fetchBoard.writer}</div>
      <div>제목 : {data?.fetchBoard.title}</div>
      <div>
        내용 :{" "}
        {process.browser && (
          <div
            dangerouslySetInnerHTML={{
              __html: Dompurify.sanitize(data?.fetchBoard.contents),
            }}
          />
        )}
        {/* process.brower */}
        {/* typeof window !== "undefined" && */}
      </div>
    </>
  );
}
profile
프론트엔드 개발자 입니다.

0개의 댓글