회사에 입사한지 한달차, 게시판을 직접 구현해야되는 업무를 맡게 되었다.
그중 텍스트에디터를 적용함에 있어 많은 시행 착오를 겪게 되었고, 결과적으로 나는 npm react-quill을 사용하게 되었다.
텍스트 에디터란 코드를 작성하고 syntax highlighting, 자동 완성, 포맷팅 등을 지원하는 개발에 최적화된 기능성 메모장이다.
내가 처음 선택한 라이브러리는 npm tinymice였다. tinymice를 선택한 이유는 아래와 같았다.
npm tinymice/react 의 사용법은 아래와 같다.
먼저 tinymice를 설치해준다.
npm i @tinymce/tinymce-react
tinymice는 api 키가 필요하기 때문에 아래의 공식 홈페이지에서 api 키를 발급받아야 된다.
https://www.tiny.cloud/docs/integrations/react/
import { Editor } from "@tinymce/tinymce-react";
export default function Edit() {
const [content, setContent] = useState("");
useEffect(() => {
// TinyMCE 초기화 설정
const apiKey = "apikey"; // Tinymce.com에서 얻을 수 있는 API 키
Editor.defaultProps.apiKey = apiKey;
getDetailData();
/**
* @param getDetailData
* 수정 페이지 세팅을 위한 디테일 데이터 호출 함수
*/
async function getDetailData() {
try {
const response = await axios.get(`/test`, {
params: {
post_id: `${slug}`, // slug 변수가 문자열인 경우
},
});
const data = await response.data;
setTitle(data[0].title);
const newFile = data.slice(1);
setFile(...newFile);
const htmlContent = `${data[0].content}`;
// 문자열을 HTML 문서로 파싱
const parser = new DOMParser();
const doc = parser.parseFromString(htmlContent, "text/html");
// body 태그 내부의 내용 추출
const bodyContent = doc.querySelector("body").innerHTML;
setContent(bodyContent);
} catch (err) {
if (err) {
alert(err);
}
}
}
}, []);
const handleContentChange = (newContent) => {
setContent(newContent);
};
return(
<Editor
value={content}
onEditorChange={handleContentChange}
init={{
height: 500,
menubar: "file edit view insert format tools table",
plugins: [
"lists",
"link",
"image",
"charmap",
"preview",
"searchreplace",
"fullscreen",
"media",
"table",
"code",
"help",
"emoticons",
"codesample",
"quickbars",
],
toolbar:
"undo redo | blocks | " +
"bold italic forecolor | alignleft aligncenter " +
"alignright alignjustify | bullist numlist outdent indent | " +
"lists table link charmap searchreplace | " +
"image media codesample emoticons fullscreen preview | " +
"removeformat | help ",
}}
/>
)
}
위와 같이 세팅을 하면 에디터가 세팅된다. 나는 이미지, 미리 보기, 전체 화면, 텍스트 수정 기능, 코드 샘플 기능 등을 세팅하고 싶어서 위와 같이 세팅했다.
이후 내가 입력한 내용을 데이터 베이스에 저장하고 싶다면, value에 세팅한 content 값을 html 형식으로 변형한 후, formdata에 함께 보내줘야 된다. 방식은 아래와 같이 세팅하였다. (추후 등록한글을 볼수있는 상세페이지에서 렌더링할때 html 파일 형식으로 렌더링 해줘야 되기 때문이다.)
const htmlContent = `<html>
<body>
${content}
</body>
</html>`;
formData.append("content", htmlContent);
formData.append("title", title);
formData.append("user_id", "관리자");
try {
const response = await axios.put(`/test`, formData, {
params: {
post_id: `${slug}`,
},
headers: {
"Content-Type": "multipart/form-data",
},
});
if (response.status === 200) {
alert("게시물이 수정 되었습니다.");
// router.back();
}
} catch (err) {
if (err) {
alert(err);
}
}
};
이미지 삽입 기능도 가능하고, 텍스트 에디터에 이미지를 첨부했다면, base64형식의 src로 변환된 이미지 태그가 반환된다.
하지만.. npm tinymice를 회사에서 원하지 않았다. 이유는 무료 정책이 월 1000회 밖에 지원돠지 않았고, 회사에서는 무료 라이브러리를 원했기 때문이다. 따라서 나는 다시 라이브러리를 꼼꼼히 찾아 나서는 여정을 떠났다.. (이 라이브러리를 사용하시는 분들은 간단한 토이 프로젝트에서 사용하시길 바랍니다..)
두 번째로 여러 라이브러리를 조사했고, 내가 선택한 라이브러리는 toast ui에서 제공하는 에디터 라이브러리였다.
선택이유
단점
이러한 단점에도 불구하고 선택했던 이유는 현재 next js를 사용하고 있지만 csr 컴포넌트를 사용하면 ssr 미지원 문제는 해결될 줄 알았고, react 18을 미지원하면 리액트 버전을 낮추면 된다고 생각했다. (현재 진행하고 있는 프로젝트에 세팅되어 있는 라이브러리들은 모두 리액트 17 이상 지원 가능하기 때문이다.) 또한 이미지 사이즈라던가, 글자 크기 상세 조정 관련한 부분은 미리 상사에게 확인받았고, 사용해도 좋다는 허락을 받았었다.
막상 라이브러리를 다운로드하고, 리액트 버전 다운그레이드 한 후 csr 컴포넌트에 세팅해 보았더니, 내가 장점이라고 생각했던 ui의 레이아웃이 깨져서 화면에 렌더링이 되었다.. 기능에는 문제가 없었지만, 사용자 입장에서 불편을 느낄만한 ui/ux였기 때문에 과감히 포기하고 사전 조사에서 후보에 있던 라이브러리를 좀 더 세심하게 분석하고 세팅하게 되었다. (next.js를 사용하고, react 18버전에서 버전 다운그레이드를 하면 안 되는 분들은 지양하시길 권장합니다.)
마지막으로 세팅하게 된 npm react-quill 라이브러리다.
선택 이유는 다음과 같다.
npm react-quill의 사용방법은 아래와 같다.
npm i react-quill
먼저 react quill을 다운로드 해준다.
import ReactQuill, { Quill } from "react-quill";
import "react-quill/dist/quill.snow.css";
export default function Edit() {
const [content, setContent] = useState("");
const modules = useMemo(
() => ({
toolbar: {
container: [
[{ header: "1" }, { header: "2" }],
[{ size: [] }],
["bold", "italic", "underline", "strike", "blockquote"],
[
{
color: [
"#000000",
"#e60000",
"#ff9900",
"#ffff00",
"#008a00",
"#0066cc",
"#9933ff",
"#ffffff",
"#facccc",
"#ffebcc",
"#ffffcc",
"#cce8cc",
"#cce0f5",
"#ebd6ff",
"#bbbbbb",
"#f06666",
"#ffc266",
"#ffff66",
"#66b966",
"#66a3e0",
"#c285ff",
"#888888",
"#a10000",
"#b26b00",
"#b2b200",
"#006100",
"#0047b2",
"#6b24b2",
"#444444",
"#5c0000",
"#663d00",
"#666600",
"#003700",
"#002966",
"#3d1466",
"custom-color",
],
},
{ background: [] },
],
[{ list: "ordered" }, { list: "bullet" }, { align: [] }],
["image"],
],
},
clipboard: {
matchVisual: false,
},
ImageResize: {
parchment: Quill.import("parchment"),
},
}),
[],
);
const formats = ["header", "font", "size", "bold", "italic", "underline", "strike", "blockquote", "list", "bullet", "align", "image"];
return(
<ReactQuill
ref={quillRef}
onChange={(html) => setContent(html)}
modules={modules}
formats={formats}
value={content}
placeholder={"내용을 입력하세요.."}
theme="snow"
/>
)
}
위와 같이 세팅해 준다. modules에는 초기 세팅할 값들을 세팅한다.
세팅을 하면서 react quill의 단점을 발견했다. 텍스트를 쓰고 드래그한 후 텍스트를 지울 경우, 텍스트를 받는 state가 <p><br></p>
로 잔여 태그들이 남게 되는 것이었다. 이러한 문제점은 분기 처리를 하여 해결하였다.
또한, 두 번째 문제점은 이미지 리사이즈 기능이 기본으로 지원이 안돼서 따로 세팅을 해줘야 되는 점이었다. 따라서 아래와 같이 세팅해 주었다.
import ReactQuill, { Quill } from "react-quill";
Quill.register("modules/ImageResize", ImageResize);
function Edit() {
const modules = useMemo(
() => ({
toolbar: {
container: [
[{ header: "1" }, { header: "2" }],
[{ size: [] }],
["bold", "italic", "underline", "strike", "blockquote"],
[
{
color: [
"#000000",
"#e60000",
"#ff9900",
"#ffff00",
"#008a00",
"#0066cc",
"#9933ff",
"#ffffff",
"#facccc",
"#ffebcc",
"#ffffcc",
"#cce8cc",
"#cce0f5",
"#ebd6ff",
"#bbbbbb",
"#f06666",
"#ffc266",
"#ffff66",
"#66b966",
"#66a3e0",
"#c285ff",
"#888888",
"#a10000",
"#b26b00",
"#b2b200",
"#006100",
"#0047b2",
"#6b24b2",
"#444444",
"#5c0000",
"#663d00",
"#666600",
"#003700",
"#002966",
"#3d1466",
"custom-color",
],
},
{ background: [] },
],
[{ list: "ordered" }, { list: "bullet" }, { align: [] }],
["image"],
],
},
clipboard: {
matchVisual: false,
},
ImageResize: {
parchment: Quill.import("parchment"),
},
}),
[],
); // 마지막에 세팅한것처럼 imageResize 세팅을 해줘야된다.
}
텍스트 에디터 뿐만 아니라, 모든 라이브러리를 이용할때, 현재 진행 중인 프로젝트의 요구 사항과 상황에 맞는 옵션을 찾기 위해서는 충분한 사전 준비와 연구가 필요하다. 필수 조건들을 명확히 정리하고, 이에 부합하는 라이브러리들을 탐색한 후, 후보 리스트를 좁혀 나가는 과정은 매우 중요하다는것을 이번 계기를 통해 깨닫게 되었다.