무료 웹 에디터 TinyMCE 사용하기
지난번 CKEditor로 image load까지 커버하려고 했으나 webpack 설정 및 다른 plugin을 사용할 경우 dependency가 더 복잡해지고 파일 수 및 용량도 많아져 좀 더 가벼운 TinyMCE로 대체해야 했다. (Next framework이랑 좀 충돌이 많기도...)
찾다보니 기본 Image 라는 OpenSource plugin에 input file uploader를 연결하여 blob형태로 이미지를 캐쉬에 저장하는 예제가 있었다. 좀 더 가볍고 손쉽게 사용이 가능한 아기자기하게 생긴 TinyMCE를 사용해보자.
1. TinyMCE 설치하기
https://www.tiny.cloud/docs/tinymce/6/react-zip-host/
1-1. TinyMCE.zip 다운로드
Cloud서비스는 사용하지 않을거기 때문에 위 링크대로, 우선 TinyMCE.zip 파일을 다운 받는다.
1-2. npm으로 TinyMCE 패키지 다운로드
npm install --save @tinymce/tinymce-react
1-3. TinyMCE.zip 압축 풀기
global.css가 들어있는 public 폴더에 tinymce 폴더를 생성한 후, 그 안에 압축을 푼다.
1-4. LanguagePack 다운로드
Language Pack을 다운로드한 후 압축을 푼 tinymce/langs 폴더에 저장한다. (ex. 한국어 - ko_KR.js)
https://www.tiny.cloud/get-tiny/language-packages/
2. Editor 구현하기
Image 플러그인 document를 보고 아래와 같이 구현했다.
https://www.tiny.cloud/docs/tinymce/6/image/
import React, { useEffect, useRef, useState } from 'react';
import { Editor } from '@tinymce/tinymce-react';
type Props = {
editorId : string //Editor 구분 ID
defaultText : string //기본문구
heightValue : number //Editor 높이
}
const WebEditor = ({editorId, defaultText, heightValue} : Props) : JSX.Element => {
const editorRef = useRef(null);
const [editorHeight, setEditorHeight] = useState(heightValue);
//이미지 로드 시 callback
const loadHandler = (callback, e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.addEventListener('load', () => {
/*
Note: Now we need to register the blob in TinyMCEs image blob
registry. In the next release this part hopefully won't be
necessary, as we are looking to handle it internally.
*/
const id = 'blobid' + (new Date()).getTime();
const blobCache = editorRef.current.activeEditor.editorUpload.blobCache;
const base64 = String(reader.result).split(',')[1];
const blobInfo = blobCache.create(id, file, base64);
blobCache.add(blobInfo);
/* call the callback and populate the Title field with the file name */
callback(blobInfo.blobUri(), { title: file.name });
});
reader.readAsDataURL(file);
}
useEffect(()=>{
setEditorHeight(heightValue);
}, [heightValue])
return (
<>
<Editor
id = {editorId}
tinymceScriptSrc={process.env.NEXT_PUBLIC_API_URL + '/tinymce/tinymce.min.js'}
onInit={(evt, editor) => editorRef.current = editor}
initialValue={defaultText}
init={{
language: 'ko_KR',
height: editorHeight,
menubar: false,
plugins: [
'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview',
'anchor', 'searchreplace', 'visualblocks', 'code',
'insertdatetime', 'media', 'table', 'help', 'wordcount', 'autosave'
],
toolbar1: 'blocks alignleft aligncenter alignright alignjustify bold italic forecolor bullist numlist outdent indent',
toolbar2: 'table link image media code restoredraft removeformat help | undo redo ',
content_style: 'body { font-family:Helvetica,Arial,sans-serif; font-size:14px }',
autosave_interval: '10s',
image_title: true,
file_picker_types: 'image',
images_file_types: 'jpg',
file_picker_callback: (cb, value, meta) => {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/jpeg');
input.addEventListener('change', (e) => {
loadHandler(cb, e);
});
input.click();
},
media_live_embeds: true,
iframe_template_callback: (data) => {
`<iframe title="${data.title}" width="${data.width}" height="${data.height}" src="${data.source}"></iframe>`
}
}}
/>
</>
);
}
export default WebEditor;
- file_picker_types : 파일픽커가 받아들일 파일 타입
- file_picker_callback : 파일픽커 호출 시 로드되는 함수
- loadHandler() : 이미지 로드시 호출되는 함수로 이미지를 Blob형태로 Editor에서 제공하는 캐쉬에 저장한다
- image_file_types : 이미지 로드할 input에서 제공되는 image extension
위 정도만 알고있으면 이미지 업로드는 문제가 아니다. 다만, 업로드 후 저장 시 서버에 어떤식으로 저장할 지는 좀 더 고민해봐야겠다.
주목할 점은 onInit()에서 useRef를 이용해 Editor를 this처럼 받아 컴포넌트 내 다른함수에서도 활용가능하도록 하는 것. 오늘도 React를 이렇게 한발짝 알아간다.
이런 훅은 대체 언제쓰지 하는 것들도 쓰다보면? 어떤 상황에 적절하게 사용할 수 있는지 감이 생기는 것 같다.
'개발 Study > React' 카테고리의 다른 글
[React/Vite] CRA(Create-React-App) 프로젝트를 Vite 로 변환하기 (0) | 2023.03.27 |
---|---|
[React] 개발 환경 설정 ESLint(eslint-config-airbnb-typescript) + Prettier + React + TypeScript (3) | 2023.03.02 |
[React/NextJS] 무료 웹 에디터 CKEditor 사용하기 (0) | 2023.01.25 |
[NextJS] KaKao Map API로 좌표 얻기- Geocode사용하기 (1) | 2023.01.13 |
[NextJS] SSR에서 Cookie 값 저장하고 가져오기 (1) | 2022.11.10 |
댓글