ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [NextJS] Tiptap(WSIWYG Editor) - NextJS 문서 편집기(Rich Editor) 구축기
    frontend/react&next 2022. 3. 2. 16:32

    매일 개인 개발을 할 때 글 쓰기를 만들 때 내 입맛대로 만들고 싶을 때 어떤 에디터를 사용할까라는 고민을 정말 많이 해왔던 것 같습니다. 저 또한 Quill, CK, TUI, Editor, TinyMCE 등 많은 에디터 들이 있지만 그 중에서 가장 제가 원하는대로 깔끔하게 커스텀 가능했던 에디터를 소개해드릴려고 합니다.

     

    Vue이용자들은 Tiptap에디터를 꽤 많이들 사용하는 것으로 알고 있습니다. Tiptap의 가장 큰 장점은 다양한 프레임워크 지원이 아닐까 싶습니다. Vue, React 뿐만 아니라 Nuxt, Next 등의 SSR에서의 환경을 제공한다는 것은 매우 만족 스러웠습니다. quill을 이용했을 때는 매번 dynamic을 써서 어거지로 넣었던 기억이 있습니다. 이 에디터는 매우 강력하고 스타터 킷을 이용해도 되지만 내가 원하는대로 대시보드를 꾸미는 과정이 매우 편했습니다. 제가 원하는 기능도 매우 깔끔하게 동작해서 기분이 좋았습니다.

     

    1. TipTap 설치

    //npm
    npm install @tiptap/core @tiptap/starter-kit
    //yarn
    yarn add @tiptap/core @tiptap/starter-kit

    우선 다운로드를 받으시고

     

    2. Editor.jsx 생성

    import { useEditor } from '@tiptap/core'
    import StarterKit from '@tiptap/starter-kit'
    
    const Tiptap = (props) => {
      const editor = useEditor({
        extensions: [StartKit],
        content: props.content
        editable:props.isEditable
      })
      return <EditorContent editor={editor}/>
    }
    export default Tiptap;

    초기 셋팅을 해줍니다. 여기서 StartKit안에는 Text와 CodeBlock 등 기본적인 필수요소들이 들어 있습니다. content에 내용이 담길 것입니다. 그리고 editable은 수정가능한 모드인지 readonly모드인지 지정할 때 필요합니다. 이 상태 까지만드시면 네모난 박스가 생기고 입력이 가능해 질것입니다. 물론! 패널은 없습니다. 계속 따라가보겠습니다.

     

    3. EditorMenu.jsx 생성

    자 그럼 에디터 메뉴를 생성해보겠습니다. 에디터 메뉴는 굉장히 간단히 생성할 수 있습니다. 뭐 span으로 조지셔도 되지만 저는 아이콘으로 꾸몄습니다. 우선 글과 똑같이 하려면 react-icons 를 다운받아 주세요.

    //npm
    npm install react-icons
    //yarn
    yarn add react-icons

    다운을 받으셨으면 아래와 같이 기본적인 작업을 해줍니다.

    import { MdFormatBold } from 'react-icons/md'
    const TipTapMenu = ({editor}) => {
      if(!eidtor) return null;
      return <div>
        <MdFormatBold size={20} 
                      cursor={"pointer"}
                      onClick={()=> editor.chain().focus().toggleBold().run()}
                      style={{ color : editor.isActive('bold') ? 'orange': 'black' }} />
      </div>
    }

    해당 기능은 에디터의 선택된 영역의 텍스트에 볼드 처리를 하는 기능입니다. onClick에 에디터의 togglebold를 run해주고 해당 부분이 bold가 활성화 되있는지 체크하여 볼드 버튼을 변경 해주고있습니다. 이외에도 엄청 많은 기능들이 있습니다. 우측의 페이지에 들어가서 원하는 기능 선택 후 read more해서 볼 수 있습니다. 더 많은 기능 보러가기 이렇게 기본적인 작업이 끝낫다면 에디터와 합쳐줍니다.

     

    4. 에디터 합치기

    import { useEditor } from '@tiptap/core'
    import StarterKit from '@tiptap/starter-kit'
    import TipTapMenu from './TipTapMenu';
    
    const Tiptap = (props) => {
      const editor = useEditor({
        extensions: [StartKit],
        content: props.content
        editable:props.isEditable
      })
      return <>
        <TipTapMenu editor={editor}/>
        <EditorContent editor={editor}/>
      <>;
    }
    export default Tiptap;

    위와 같이 셋팅하시고 원하는 모양대로 styledComponent나 아니면 기타 방식으로 작업해주시면 될 것 같습니다.

     

    5. 내용 가져오기

    //html
    editor.getHTML();
    //json
    editor.getJSON();

    내용을 가져올때는 위와 같이 간단히 사용할 수 있습니다. 하지만 이런 에디터는 외부에서 요청하는 경우가 많습니다. 그럴때는 React의 useRef를 이용해 줍니다.

     

    호출 하는 곳

    import TipTap from 'TipTap컴포넌트(메뉴포함)경로'
    import { useRef } from 'react';
    
    const container = () => {
      const editorRef = useRef();
      
      const getContent = () => {
      	console.log(editorRef.current.props.editor.getHTML());
      }
      
      return <>
        <TipTap editorRef={editorRef} />
        <button onClick={getContent}>내용 가져오기</button>
      <>
    }
    export default container;

    TipTap.js

    import { useEditor } from '@tiptap/core'
    import StarterKit from '@tiptap/starter-kit'
    import TipTapMenu from './TipTapMenu';
    
    const Tiptap = (props) => {
      const editor = useEditor({
        extensions: [StartKit],
        content: props.content
        editable:props.isEditable
      })
      return <>
        <TipTapMenu editor={editor}/>
        <EditorContent editor={editor} ref={props.editorRef} />
      <>;
    }
    export default Tiptap;

    위와 같이 작업하면 참조가 걸려서 데이터를 가져가실 수 있습니다. 또한, 이외에도 데이터를 SET하고 싶을때도 분명 있겠죠? 그럴때는 아래와 같은 명령어를 사용할 수 있습니다.

     editorRef.current.props.editor.commands.setContent("");

    setContent를 이용하며 데이터를 넣을 수 있습니다. 특정 상황에서 데이터 변경이 제대로 되지 않는다면 TipTap.js안에 useEffect를 이용해 데이터 갱신을 직접 해줄 수 있습니다.

    useEffect(() => {
      editor && editor.commands.setContent(String(props.content));
    }, [props.content])

    이렇게 사용할 수 있습니다. TipTap을 사용해본 결과 매우 만족스러웠습니다. NextJS를 지원하는것도 감사하고 커스텀 하기 정말 편했던 것 같습니다.

     

    실제로 이것을 사용한 FullSource를 보고싶으시면 아래 프로젝트의 컴포넌트 :  src/components/common => TipTap.tsx / TipTapMenu.tsx / 호출부 : src/containers/issue/DetailContainer.tsx 를 참고하시면 될 것 같습니다.

     

    [알림 : 2022-09-06 22:20] 오탈자 1차 수정완료 된 글입니다. 처음 편집기 쓰시는 분들을 위해 가이드를 작성한다는게 되려 혼란을 드려 죄송합니다. 알려주신 분 감사합니다.

    - 코드 오탈자는 1차적으로 검토해 수정 하였지만, 추가적으로 코드 작성시 에러가 난다거나 하시면 댓글로 말해주시면 지속적으로 피드백 및 수정 하겠습니다. 글의 퀄리티와 다음 글을 보게될 개발자 분들을 위해 오탈자나 소스 미동작시 바로 말씀해주시면 됩니다. (하루내로 피드백 드리겠습니다.)

    - 아래 링크의 실제 구현 코드를 참고하시면 더욱 도움이 되실 수 있습니다. 제가 구현했던 코드를 기반으로 코드를 복붙하여 작성한 것이 아니라 직접 작성한것이라 오탈자가 있을 수 있으니 참고 바랍니다.

     

    https://github.com/ryuhojin/INT-FRONT

    반응형

    댓글

Designed by Tistory.