Next.js

[Next.js] TinyMCE 라이브러리 사용하기 (+ 이미지 업로드)

lheunoia 2022. 8. 27. 23:30

 

 

 

The Most Advanced WYSIWYG HTML Editor | Trusted Rich Text Editor | TinyMCE

TinyMCE is the most advanced WYSIWYG HTML editor designed to simplify website content creation. The rich text editing platform that helped launched Atlassian, Medium, Evernote and more.

www.tiny.cloud

 

 

 

왜 TinyMCE 라이브러리를 선택했을까?

TinyMCE 에디터는 무료 버전도 있고 가장 많이 사용되는 CKeditor보다 가벼워서 선택하게 되었습니다. 무엇보다 티스토리 블로그에서도 TinyMCE 에디터를 사용하고 있었기 때문에 참고 자료로 활용하기 좋을 것 같았죠.

 

 

 

 

 

📗 TinyMCE 사용 방법을 알아보자

 

1. tinymce 설치

npm i tinymce @tinymce/tinymce-react

 

 

 

 

2. TinyMceEditor 컴포넌트 작성

// src/components/editor/tinymce/TinymceEditor.tsx

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

const TinyMceEditor = () => {
	const editorRef = useRef(null);

  	const tinymcePlugins = ['link', 'lists', 'autoresize'];
  	const tinymceToolbar =
    	'blocks fontfamily |' +
    	'bold italic underline strikethrough forecolor backcolor |' +
    	'alignleft aligncenter alignright alignjustify |' +
    	'bullist numlist blockquote link';
    
	return (
    	<Editor
        onInit={(e, editor) => (editorRef.current = editor)}
        init={{
          plugins: tinymcePlugins,
          toolbar: tinymceToolbar,
          min_height: 500,
          menubar: false,
          branding: false,
          statusbar: false,
          block_formats: '제목1=h2;제목2=h3;제목3=h4;본문=p;'
        }}
      />
    );
};

export default TinymceEditor;

 

이제 Next.js 프로젝트에서 TinyMCE 에디터를 사용할 수 있습니다!

 

 

 

 

 

3. 이미지 업로드 기능 구현 (+ 보너스)

TinyMCE 에디터에 이미지를 업로드하는 기능도 추가해 보았습니다.

 

 

 

 

src/components/editor/tinymce/TinymceEditor.tsx

import React, { FC, useRef } from 'react';
import { useRecoilState } from 'recoil';

import { tinymceEditorState } from '../../../recoil/tinymce';

const TinyMceEditor:FC<TinymceEditorProps> = ({ onOpenFile }) => {
	const editorRef = useRef(null);
	const [tinymceEditor, setTinymceEditor] = useRecoilState(tinymceEditorState);

	return (
		<Editor
	    onInit={(e, editor) => (editorRef.current = editor)}
	    init={{
	      plugins: tinymcePlugins,
	      toolbar: tinymceToolbar,
			  // 생략..
	
	      setup(editor) {
	        setTinymceEditor(editor); // activeEditor를 전역적으로 관리
			
            // 이미지를 업로드하는 버튼 추가
	        editor.ui.registry.addButton('image-upload', {
	          icon: 'image',
	          tooltip: '업로드',
	          onAction: () => {
	            editor.execCommand('image-upload');
	          },
	        });
	
	        editor.addCommand('image-upload', onOpenFile); // onOpenFile 함수 실행
	      },
	    }}
	  />
	);
};

export default TinymceEditor;

 

우선 위와 같이 코드를 수정해주세요.

에디터가 실행될 때 가장 처음 실행되는 함수인 setup 함수에서 setTinymceEditor(editor)를 수행하여 tinymceEditorState 값에 activeEditor 값을 저장했습니다.

 

(Recoil의 개념을 모르시는 분들은 이 글을 참고하고 오셔도 좋을 것 같아요. 😊)

 

 

 

 

 

 

그런데 여기서 잠깐, 저의 우여곡절을 함께 볼까요?

(해결 방안만 보고 싶다면 🖼 Recoil을 사용하여 이미지 업로드하기부터 보시면 됩니다.)

 

 

에러 발생 코드

import tinymce from 'tinymce';

tinymce.ActiveEditor.execCommand('mceInsertContent', 'false', '<img src="' + imageSrc + '" />');

 

만약 이미지를 업로드하려고 할 때 tinymce 라이브러리를 import하고 위 코드처럼 코드를 작성한다면 (이미지를 에디터에 보이게 하기 위해), 처음에는 문제없이 잘 수행될 것입니다. 그러나 에디터 페이지를 새로고침을 한다면 navigator is not defined 에러와 함께 에디터 페이지를 더 이상 볼 수 없습니다. 왜 그럴까요?

 

 

 

Next.js에서 새로고침은 SSR 방식으로 동작하기 때문에 서버에서 tinymce 모듈을 불러오려고 할테지만 tinymce는 SSR을 지원하지 않기 때문에 navigator가 존재하지 않는 서버에서는 tinymce 모듈을 찾을 수가 없는 거죠. 이 문제를 해결하기 위해 떠올린 방법은 Recoil 라이브러리를 사용해서 tinymce.activeEditor 값을 전역으로 관리하자! 였습니다.

 

 

 

 

 

🖼 Recoil을 사용하여 이미지 업로드하기

 

(1) recoil 설치

npm i recoil

recoil을 사용하기 위해서는, 먼저 recoil을 프로젝트에 설치해야 합니다.

 

 

 

 

(2) atom 생성

 

src/recoil/tinymce.ts

import { atom } from 'recoil';
import { Editor } from 'tinymce';

export const tinymceEditorState = atom<Editor>({
  key: 'tinymceEditor',
  default: null,
  dangerouslyAllowMutability: true,
});

tinymce.activeEditor 객체를 저장할 tinymceEditorState atom을 생성합니다.

 

 

 

 

 

(3) 이미지 업로드 실행

 

src/components/editor/Editor.tsx

import { useRecoilValue } from 'recoil';
import { tinymceEditorState } from '../../recoil/tinymce';

const Editor = () => {
	const tinymceEditor = useRecoilValue(tinymceEditorState); // atom state
    
    	const handleUploadImage = async (imageUrl: string, filename: string) => {
    		const dom = tinymceEditor.dom;

	    	tinymceEditor.execCommand(
    	  		'mceInsertContent',
      			false,
      			'<img src="' + imageUrl + '" data-filename="' + filename + '" />'
    		);
	};
  
	...more code
};

TinymceEditor 컴포넌트에서 tinymceEditorState atom을 업데이트했기 때문에 해당 atom을 구독하고 있던 Editor 컴포넌트가 업데이트된 state로 re-render되고, tinymceEditorState에 저장된 activeEditor 객체를 참조하여 명령어를 수행할 수 있습니다!

 

 

 

 

 

 

 

반응형