Nextjs QuillEditor 적용하는방법
1. 모듈 입력
npm install react-quill // npm install quill
2. 모듈주입하여 사용
import ReactQuill from "react-quill";
function Write() {
return (
<>
<ReactQuill />
</>
);
}
export default Write;
index.html
...
<link
rel="stylesheet"
href="https://unpkg.com/react-quill@1.3.3/dist/quill.snow.css"
/>
...
3.이미지 사이즈 조정할 수 잇게할것
npm i quill-image-resize-module-react
import ReactQuill, { Quill } from "react-quill";
import ImageResize from "quill-image-resize-module-react";
Quill.register("modules/imageResize", ImageResize);
const modules = {
toolbar: {
container: toolbarOptions,
},
imageResize: {
// https://www.npmjs.com/package/quill-image-resize-module-react 참고
parchment: Quill.import("parchment"),
modules: ["Resize", "DisplaySize", "Toolbar"],
},
};
4.최종 컴포넌트 만들어서 사용함
'use client'
import hljs from 'highlight.js'
import "highlight.js/styles/github.css";
hljs.configure({
languages: ['javascript', 'ruby', 'python', 'java', 'cpp', 'kotlin', 'sql']
});
import React, { useEffect,useState, useRef } from 'react';
import ReactQuill, { Quill } from "react-quill";
import ImageResize from "quill-image-resize-module-react";
import { request } from '../utils/network';
import showPopup from './PopUp';
import { getCookie } from '@/utils/cookie';
import { Delta } from 'quill';
import "@/css/quill.css"
import "react-quill/dist/quill.snow.css";
Quill.register("modules/imageResize", ImageResize);
// import 'highlight.js/styles/default.css';
// import 'highlight.js/styles/monokai-sublime.css';
const fontSizeArr = ['8px','9px','10px','12px','14px','16px','20px','24px','32px','42px','54px','68px','84px','98px'];
var Size = Quill.import('attributors/style/size');
Size.whitelist = fontSizeArr;
Quill.register(Size, true);
export default function QuillEditor({ placeholder, value, setData }) {
const quillRef = useRef(null);
// 사용하고 싶은 옵션, 나열 되었으면 하는 순서대로 나열
const toolbarOptions = [
[{ header: '1' }, { header: '2' }, { font: [] }],
['code-block'],
[{ 'size': fontSizeArr }],
// [{ size: ["small", false, "large", "huge"] }],
[{ header: [1, 2, 3, 4, 5, 6, false] }],
["bold", "italic", "underline", "strike"],
["blockquote"],
[{ list: "ordered" }, { list: "bullet" }],
[{ 'direction': 'rtl' }],
[{ 'script': 'sub'}, { 'script': 'super' }],
['clean'],
[{ color: [] }, { background: [] }],
[{ align: [] }],
[{ 'indent': '-1'}, { 'indent': '+1' }], // outdent/indent
['blockquote', 'link', 'code-block', 'formula', 'image', 'video'],
['horizontal-line','html']
];
// 옵션에 상응하는 포맷, 추가해주지 않으면 text editor에 적용된 기능을 쓸스도 볼수 없음
const formats = [
"header",
"font",
"size",
"bold",
"italic",
"underline",
"strike",
"align",
"blockquote",
"list",
"bullet",
"indent",
"background",
"color",
"link",
"image",
"video",
"width",
"code-block",
"horizontal-line",
"html"
];
const modules = {
toolbar: {
container: toolbarOptions,
},
// syntax: {
// highlight: text => hljs.highlightAuto(text).value
// },
// syntax: {
// highlight: (text) => {
// return hljs.highlightAuto(text).value;
// },
// },
// highlight: true,
imageResize: {
parchment: Quill.import("parchment"),
modules: ["Resize", "DisplaySize", "Toolbar"],
},
// syntax: {
// highlight: (text) => {
// return hljs.highlightAuto(text).value; // highlight 함수를 호출하고 반환 값을 리턴
// },
// },
};
useEffect(() => {
const handleImage = () => {
// console.log('image')
const input = document.createElement("input");
input.setAttribute("type", "file");
input.setAttribute("accept", "image/*");
input.click();
input.onchange = async () => {
const file = input.files[0];
const editor = quillRef.current.getEditor();
const range = editor.getSelection();
// 서버에 올려질때까지 표시할 로딩 placeholder 삽입
editor.insertEmbed(range.index, "image", `/assets/img/loading_gif.gif`);
try {
// 필자는 파이어 스토어에 저장하기 때문에 이런식으로 유틸함수를 따로 만들어줬다
// 이런식으로 서버에 업로드 한뒤 이미지 태그에 삽입할 url을 반환받도록 구현하면 된다
let url = "";
const formData = new FormData();
let jsonData = {company_key:getCookie("company_key")}
formData.append("json", new Blob([JSON.stringify(jsonData)], { type: "application/json" }));
formData.append('contents_img', file);
formData.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
let NETWORK_API_URL = process.env.NEXT_PUBLIC_REACT_APP_API_URL + process.env.NEXT_PUBLIC_REACT_APP_API_SPEAKY_CONTENTS_UPLOAD_URL;
console.log(NETWORK_API_URL, formData)
const response = await request(NETWORK_API_URL, 'POST', formData);
console.log('응답 값', NETWORK_API_URL, response);
if (response.resultCode !== "200") {
showPopup("정부수정 경고 ", response.resultCode, () => {
console.log('Popup Closed!');
});
const errorData = await response.json();
throw new Error(errorData.message || '서버에 문제가 있습니다.');
} else {
url = response.res;
console.log(url);
}
// const url = await uploadImage(file, filePath);
// 정상적으로 업로드 됐다면 로딩 placeholder 삭제
editor.deleteText(range.index, 1);
// 받아온 url을 이미지 태그에 삽입
editor.insertEmbed(range.index, "image", url);
// 사용자 편의를 위해 커서 이미지 오른쪽으로 이동
editor.setSelection(range.index + 1);
} catch (e) {
editor.deleteText(range.index, 1);
}
};
}
const handleHr = () => {
console.log('horontal line')
const range = quill.getSelection();
quill.updateContents(new Delta()
.retain(range.index)
.delete(range.length)
.insert({ horizontalLine: true })
);
}
if (quillRef.current) {
const toolbar = quillRef.current.getEditor().getModule("toolbar");
toolbar.addHandler("image", handleImage);
toolbar.addHandler('horizontal-line', handleHr);
// var ColorClass = Quill.import('attributors/class/color');
// var SizeStyle = Quill.import('attributors/style/size');
// Quill.register(ColorClass, true);
// Quill.register(SizeStyle, true);
// var FontAttributor = Quill.import('attributors/class/font');
// FontAttributor.whitelist = [
// 'sofia', 'slabo', 'roboto', 'inconsolata', 'ubuntu'
// ];
// Quill.register(FontAttributor, true);
}
}, []);
return (
<>
<link
rel="stylesheet"
href="https://unpkg.com/react-quill@1.3.3/dist/quill.snow.css"
/>
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet"></link>
<div className='editor-container'>
<ReactQuill
ref={quillRef}
style={{ width: "100%", height:"500px", minHeight:"500px", maxHeight:"550px" }}
placeholder={placeholder}
value={value || ""}
theme="snow"
modules={modules}
formats={formats}
onChange={setData}
/>
</div>
</>
);
}
몇가지 고난사항.
1. Toolbaroptions, Format 사항에 넣어줘야 제대로 필요한 기능이 랜더링된다.
2.이미지 첨부시 서버쪽 소스 참고및 로딩이미지 imbed
3.에디터의 탭은, 제대로 적용되지않아 스타일로 조정하여 처리함


