커뮤니티 사이트를 만드는 중에 게시물 업로드를 위한 무료 에디터로 quill을 선택했고, database에 무거운 이미지를 저장하는 것은 부적절 하다고 판단하여 이미지 저장소는 s3로 따로 분리하는 과정을 기록하였다.
quill 기본설정에서는 이미지를 base64로 가져오게 되어있어 커스텀으로 핸들링하는 과정이 필요하다.
즉 이미지 가져오기 클릭 -> 이미지를 s3에 저장 -> 저장된 url을 가져와 서버로 보내고 데이터베이스에 저장. -> 게시물 업로드 후 불러올 때 url을 가져와 사용.
결과적으로 react-quill을 사용하는 것은 functionall component에서 이미지를 커스텀으로 핸들링하기 어렵기 때문에 순수 quill을 사용하여 구현하는 것이 좋다.
nextjs에서는 quill을 불러올 때 pre-rendering 환경에서 오류가 발생하기 때문에 pre-render 이후 csr로 가져온다.
// components/QuillEditor.tsx
import React, { useEffect, useRef } from 'react';
import S3 from 'react-aws-s3';
export const modules = {
toolbar: {
container: [
['bold', 'italic', 'underline', 'strike'], // toggled buttons
['blockquote', 'code-block'],
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
[{ 'script': 'sub' }, { 'script': 'super' }], // superscript/subscript
[{ 'indent': '-1' }, { 'indent': '+1' }], // outdent/indent
[{ 'direction': 'rtl' }], // text direction
[{ 'size': ['small', false, 'large', 'huge'] }], // custom dropdown
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
[{ 'color': [] }, { 'background': [] }], // dropdown with defaults from theme
[{ 'font': [] }],
[{ 'align': [] }],
['link', 'image', 'formula'],
['clean'],
],
},
};
function QuillEditor({ QuillChange }) {
const Quill = typeof window == 'object' ? require('quill') : () => false;
const quillElement = useRef(null);
const quillInstance = useRef(null);
const onClickImageBtn = () => {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.click();
input.onchange = function () {
const file = input.files[0];
const fileName = file.name;
const config = {
bucketName: process.env.S3_BUCKET_NAME,
region: process.env.S3_REGION,
accessKeyId: process.env.S3_ACCESS_ID,
secretAccessKey: process.env.S3_ACCESS_KEY,
};
const ReactS3Client = new S3(config);
ReactS3Client.uploadFile(file, fileName).then((data) => {
if (data.status === 204) {
//커서 위치 받아오기 위함.
const range = quillInstance.current.getSelection(true);
// 1.현재 커서 위치에 2. 이미지를 3.src="" 로 나타냄.
quillInstance.current.insertEmbed(
range.index,
'image',
`${data.location}`
);
// 이미지 업로드 후 커서 이미지 한칸 옆으로 이동.
quillInstance.current.setSelection(range.index + 1);
} else {
alert('error');
}
});
};
};
useEffect(() => {
if (quillElement.current) {
quillInstance.current = new Quill(quillElement.current, {
theme: 'snow',
placeholder: 'Please enter the contents.',
modules: modules,
});
}
const quill = quillInstance.current;
quill.on('text-change', () => {
QuillChange(quill.root.innerHTML);
});
const toolbar = quill.getModule('toolbar');
toolbar.addHandler('image', onClickImageBtn);
}, []);
return (
<>
<div ref={quillElement}></div>
</>
);
}
export default React.memo(QuillEditor);
우와 효식님 멋진 프로젝트 하셨습니다...!!!! 글 잘보고 갑니다!