본문 바로가기

dev/React

React에서 TinyMCE 셋업하기

TinyMCE는 현재 기준 꽤 쓸만한 오픈소스 에디터라고 생각합니다. 다만, 클라우드 호스팅된 서비스를 사용하려면 비용을 내야 합니다. 무료 플랜을 제공하고 있으므로 개인 프로젝트 등에서 간단히 사용할 예정이라면 무료 플랜을 사용해 손쉽게 세팅하면 됩니다. 만약 사용자가 많은 서비스인 경우 셀프 호스팅이나 번들링을 통해 무료로 사용이 가능합니다. 본 포스팅에서는 React환경 기반에서 번들링을 통해 TinyMCE를 셋업하는 방법을 설명합니다.

설치

tinymce, @tinymce/tinymce-react, raw-loader를 설치합니다

npm install --save tinymce @tinymce/tinymce-react raw-loader

 

번들 컴포넌트 구현

tinymce, tinymce-react를 포함하는 컴포넌트를 구성해줍니다. 기본적으로 tinymce를 import하고, 플러그인, 플러그인 리소스, 기타 스타일 리소스를 import하는 순서입니다. 주의할 점은 tinymce를 글로벌 변수로 사용할 수 있게 세팅해 주어야 합니다. 공식 문서 예제처럼 상단에 import 하는 방법도 있지만, 저는 import 순서에 따라 오류가 발생할 수 있다는 점이 싫어서 App.js에서 window.tinymce에 직접 할당해 주었습니다. 아래는 공식 홈페이지의 예제입니다.

import { Editor } from '@tinymce/tinymce-react';

// TinyMCE so the global var exists
// eslint-disable-next-line no-unused-vars
import tinymce from 'tinymce/tinymce';
// DOM model
import 'tinymce/models/dom/model'
// Theme
import 'tinymce/themes/silver';
// Toolbar icons
import 'tinymce/icons/default';
// Editor styles
import 'tinymce/skins/ui/oxide/skin.min.css';

// importing the plugin js.
// if you use a plugin that is not listed here the editor will fail to load
import 'tinymce/plugins/advlist';
import 'tinymce/plugins/anchor';
import 'tinymce/plugins/autolink';
import 'tinymce/plugins/autoresize';
import 'tinymce/plugins/autosave';
import 'tinymce/plugins/charmap';
import 'tinymce/plugins/code';
import 'tinymce/plugins/codesample';
import 'tinymce/plugins/directionality';
import 'tinymce/plugins/emoticons';
import 'tinymce/plugins/fullscreen';
import 'tinymce/plugins/help';
import 'tinymce/plugins/image';
import 'tinymce/plugins/importcss';
import 'tinymce/plugins/insertdatetime';
import 'tinymce/plugins/link';
import 'tinymce/plugins/lists';
import 'tinymce/plugins/media';
import 'tinymce/plugins/nonbreaking';
import 'tinymce/plugins/pagebreak';
import 'tinymce/plugins/preview';
import 'tinymce/plugins/quickbars';
import 'tinymce/plugins/save';
import 'tinymce/plugins/searchreplace';
import 'tinymce/plugins/table';
import 'tinymce/plugins/template';
import 'tinymce/plugins/visualblocks';
import 'tinymce/plugins/visualchars';
import 'tinymce/plugins/wordcount';

// importing plugin resources
import 'tinymce/plugins/emoticons/js/emojis';

// Content styles, including inline UI like fake cursors
/* eslint import/no-webpack-loader-syntax: off */
import contentCss from '!!raw-loader!tinymce/skins/content/default/content.min.css';
import contentUiCss from '!!raw-loader!tinymce/skins/ui/oxide/content.min.css';

export default function BundledEditor(props) {
  const {init, ...rest} = props;
  // note that skin and content_css is disabled to avoid the normal
  // loading process and is instead loaded as a string via content_style
  return (
    <Editor
      init={{
        ...init,
        skin: false,
        content_css: false,
        content_style: [contentCss, contentUiCss, init.content_style || ''].join('\n'),
      }}
      {...rest}
    />
  );
}

 

에디터 구현

번들 컴포넌트를 사용해 실제 에디터 컴포넌트를 구현합니다. 마찬가지로 공식 홈페이지의 예시를 첨부합니다. 주의할 점은 plugins가 string배열이라는 점 입니다. 아직 문서가 제대로 정리되지 않았는지 일부 예시에서 그냥 string으로 표현되어 있는 경우가 있습니다. 공식 예제를 참조할 때 이러한 부분을 유의하시기 바랍니다.

import React, { useRef } from 'react';
import BundledEditor from './BundledEditor'

export default function App() {
  const editorRef = useRef(null);
  const log = () => {
    if (editorRef.current) {
      console.log(editorRef.current.getContent());
    }
  };
  return (
    <>
      <BundledEditor
        onInit={(evt, editor) => editorRef.current = editor}
        initialValue='<p>This is the initial content of the editor.</p>'
        init={{
          height: 500,
          menubar: false,
          plugins: [
            'advlist', 'anchor', 'autolink', 'help', 'image', 'link', 'lists',
            'searchreplace', 'table', 'wordcount'
          ],
          toolbar: 'undo redo | blocks | ' +
            'bold italic forecolor | alignleft aligncenter ' +
            'alignright alignjustify | bullist numlist outdent indent | ' +
            'removeformat | help',
          content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }'
        }}
      />
      <button onClick={log}>Log editor content</button>
    </>
  );
}