Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
Tags
- nextjs
- ReactContextAPI
- reactjs
- Nodejs
- state전역관리
- 페이스북개발자
- API루트
- 웹소켓
- 쿠키관리
- socketIO
- 플래닛스케일
- mysqlworkbench
- next js
- PlanetScale
- 넥스트js
- 비디오스트리밍
- nextjs13
- 노드메일러
- expressjs
- pyqt5
- 리액트
- ReactQuill
- 인스타그램API
- pyinstaller
- 앱비밀번호
- nodemailer
- APIroutes
- 비디오전송
- 인스타그램앱만들기
- 넥스트JS13
Archives
- Today
- Total
Timpossible history
[React JS] React-quill로 WYSIWYG 구현하는 중에 format과 blot 커스터마이징 본문
- React-quill로 Text editor 구현 중 기존 module을 이용 시 느꼈던 한계
- 그에 따른 해결책(Quill.import 메소드 사용)
- 응용 사례
- 주의 사항
1. React-quill로 Text editor 구현 중 기존 module을 이용 시 느꼈던 한계
이를 해결하기 위해 image icon 클릭 시 실행되는 코드 구현
const formats = [
"font",
"size",
"bold",
"italic",
"underline",
"strike",
"align",
"blockquote",
"list",
"bullet",
"indent",
"background",
"color",
"link",
"image",
"width",
"classify",
"video",
];
const EditorForm = ({}) => {
const quillRef = useRef(null);
const [text, setText] = useState("");
const handleImage = useCallback(() => {
const _editor = quillRef.current.getEditor();
// file type의 input을 생성 후 실행
const imageInput = document.createElement("input");
imageInput.setAttribute("type", "file");
imageInput.setAttribute("accept", "image/*");
imageInput.setAttribute("multiple", "");
imageInput.click();
imageInput.onchange = async () => {
const imageFiles = imageInput.files;
const formData = new FormData();
Array.from(imageFiles).forEach((file) => {
formData.append("image", file);
});
const range = _editor.getSelection(true);
try {
// 이미지들을 업로드 하고 저장된 url을 불러옴
const dataList = await uploadImage(formData);
dataList.files.forEach((item) => {
// inserEmbed 메소드로 editor에 렌더링해줌
_editor.insertEmbed(
range.index,
"image", // react-quill의 image 모듈을 사용
item.location
);
});
_editor.setSelection(range.index + 1); // 커서를 삽입된 이미지 옆에 위치시킴.
} catch (e) {
onAlert("이미지 업로드를 실패했습니다. 다시 시도하여 주세요");
}
};
}, []);
// video 파일 업로드도 같은 방식으로 실행
const handleVideo = useCallback(() => {
const _editor = quillRef.current.getEditor();
const videoInput = document.createElement("input");
videoInput.setAttribute("type", "file");
videoInput.setAttribute("accept", "video/*");
videoInput.setAttribute("multiple", "");
videoInput.click();
videoInput.onchange = async () => {
const videoFiles = videoInput.files;
const formData = new FormData();
Array.from(videoFiles).forEach((file) => {
formData.append("video", file);
});
const range = _editor.getSelection(true);
try {
const dataList = await uploadVideo(formData);
dataList.forEach((item) => {
_editor.insertEmbed(range.index, "customVideo", // 후에 기술할 커스터마이징한 새로운 format
item.location
);
});
_editor.setSelection(range.index + 1);
} catch (e) {
onAlert("비디오 업로드를 실패했습니다. 다시 시도하여 주세요");
}
};
}, []);
const modules = {
toolbar: {
container: "#toolbar",
handlers: {
video: handleVideo, // 기존 reactquill video 모듈에 handleVideo 함수 연결
image: handleImage, // 기존 reactquill image 모듈에 handleImage 함수 연결
},
},
imageResize: {
parchment: Quill.import("parchment"),
modules: ["Resize", "DisplaySize"],
},
};
return (
<ReactQuill
className={styles.editor}
ref={quillRef}
value={text}
theme="snow"
modules={modules}
formats={formats}
onChange={(content, delta, source, editor) => {
setText(content);
}}
preserveWhitespace
/>
);
};
◆ 한계
- ReactQuill 컴포넌트에 렌더링되는 이미지와 비디오의 크기 및 정렬 상태가 파일의 자체 사이즈에 따라서 판이하게 달라져서 보기가 불편하고 지저분해 보임.
- 렌더링되는 DOM을 style 변경이 가능하도록 커스터마이징할 필요가 있었음
2. 그에 따른 해결책(Quill.import 메소드 사용)
React-quill은 format과 blots을 import해서 내가 원하는대로 Embed 삽입이 가능함.
import ReactQuill, { Quill } from "react-quill";
// 기존 formats/video 호출이 아닌 blots/block/embed 호출
const Video = Quill.import("blots/block/embed");
const Image = Quill.import("formats/image");
class CustomVideo extends Video {
static create(value) {
const node = document.createElement("div");
node.setAttribute("style", "width:100%;");
const container = document.createElement("div");
container.setAttribute("class", "cv_container");
container.setAttribute(
"style",
"background-color:black; position:relative; padding-bottom:56.25%; padding-top:30px; height:0; overflow:hidden; width:100%; max-width:500px; margin: 0 auto;"
);
const video = document.createElement("video");
video.setAttribute("class", "cv_video");
video.setAttribute("src", value.url);
video.setAttribute("controls", "true");
video.setAttribute(
"style",
"position:absolute; top:0; left:0; width: 100%; height: 100%; margin: 0 auto;"
);
video.setAttribute("type", `video/${getExt(value.url)}`);
container.appendChild(video);
node.appendChild(container);
return node;
}
static value(node) {
return { url: node.getAttribute("src") };
}
}
// 이 property들에 고유의 이름으로 데이터 값을 주고 등록해줘야 나중에 데이터를 다시 읽을 때 충돌이 안 일어남
CustomVideo.blotName = "customVideo";
CustomVideo.className = "ql-custom-video";
CustomVideo.tagName = "video";
// 새로운 blot 커스터마이징해서 Quill 인스턴스에 등록
Quill.register(CustomVideo);
class CustomImage extends Image {
static create(value) {
let node = super.create(value);
node.setAttribute("style", "width: 100%; max-width: 550px;");
let container = document.createElement("div");
container.setAttribute("style", "text-align: center;");
container.appendChild(node);
return container;
}
}
// 기존 image format을 불러와서 커스터마이징한 것이니 그대로 등록만 해주면 됨
Quill.register(CustomImage, true);
const formats = [
"font",
"size",
"bold",
"italic",
"underline",
"strike",
"align",
"blockquote",
"list",
"bullet",
"indent",
"background",
"color",
"link",
"image", //CustomImage 클래스가 기존 image format에 연결됨
"width",
"classify",
"video", // 기존 video format은 youtube 링크 삽입용으로 사용
"customVideo", // 새로운 CustomVideo format 등록
];
const EditorForm = ({}) => {
....
return (
<ReactQuill
className={styles.editor}
ref={quillRef}
value={text}
theme="snow"
modules={modules}
formats={formats}
onChange={(content, delta, source, editor) => {
setText(content);
}}
preserveWhitespace
/>
);
};
Quill.import(formats/image)와 Quill.import(blots/block/embed)를 이용하여 새로운 형태의 DOM을 정의하고 스타일링을 추가해주니 내가 원한대로 렌더링이 됨.
3. 응용 사례
이뿐 아니라 Quill.import(blots/embed)를 사용하면 더 Rich한 DOM 생성이 가능함.
- blots/block/embed : 동영상 같은 블록 요소들을 커스터마이징할 때 용이함.
- blots/embed : 링크나 이미지 같은 인라인 요소들을 커스터마이징할 때 용이함.
필자의 사례로는 Text editor에 링크를 입력하거나 붙여넣기할 때 사이트의 메타데이터를 읽어서 렌더링하는데에 사용함.
const Embed = Quill.import("blots/embed");
class LinkPreviewBlot extends Embed {
static create(value) {
let node = super.create();
node.setAttribute(
"style",
`width: 100%; max-width:600px; border: 1px solid lightgrey; border-radius: 5px; -webkit-box-shadow: 2px 2px 6px 0px #000000; box-shadow: 2px 2px 6px 0px #000000;`
);
const link = document.createElement("a");
link.setAttribute("href", value.link);
link.setAttribute("target", "_blank");
link.setAttribute("rel", "noopener noreferrer");
link.setAttribute("style", "display: flex; text-decoration: none;");
const imgContainer = document.createElement("div");
link.setAttribute("class", "imgContainer");
imgContainer.setAttribute(
"style",
"flex: 1 1 20%; padding: 0 4px; border-right: 1px solid lightgrey;"
);
const img = document.createElement("img");
img.setAttribute("class", "preview_img");
img.setAttribute("src", value.image);
img.setAttribute("style", "width:100%; height: 100%; border-radius: 5px;");
imgContainer.appendChild(img);
const descContainer = document.createElement("div");
descContainer.setAttribute("class", "descContainer");
descContainer.setAttribute(
"style",
"display: flex; flex-direction: column; justify-content: center; flex: 1 1 80%; padding: 4px;"
);
const siteName = document.createElement("p");
siteName.innerText = value.siteName;
siteName.setAttribute("class", "preview_siteName");
siteName.setAttribute(
"style",
"font-weight: bold; color: black; padding:0 0 2px 0; margin: 0; font-size: 15px;"
);
descContainer.appendChild(siteName);
const title = document.createElement("p");
title.innerText = value.title;
title.setAttribute("class", "preview_title");
title.setAttribute(
"style",
"color: black; padding:2px 0 2px 0; margin: 0; font-size: 15px;"
);
descContainer.appendChild(title);
const siteUrl = document.createElement("p");
siteUrl.innerText = value.siteUrl;
siteUrl.setAttribute("class", "preview_siteUrl");
siteUrl.setAttribute(
"style",
"font-weight: bold; color: grey; padding: 2px 0 0 0; margin: 0; font-size: 15px;"
);
descContainer.appendChild(siteUrl);
link.appendChild(imgContainer);
link.appendChild(descContainer);
node.appendChild(link);
return node;
}
static value(node) {
return {
link: node.querySelector(".imgContainer")?.getAttribute("href"),
image: node.querySelector(".preview_img")?.getAttribute("src"),
title: node.querySelector(".preview_title")?.innerText,
siteUrl: node.querySelector(".preview_siteUrl")?.innerText,
siteName: node.querySelector(".preview_siteName")?.innerText,
};
}
}
LinkPreviewBlot.blotName = "customBlock";
LinkPreviewBlot.tagName = "div";
LinkPreviewBlot.className = "link-preview";
Quill.register(LinkPreviewBlot, true);
const formats = [
...,
"customBlock"
]
4. 주의 사항
Quill의 여러 format들을 import해서 커스터마이징한 후 등록하고 그 컴포넌트를 생성하다보니 커스터마이징한 format들의 property들을 명확하게 설정해주지 않으면 혼동이 일어나서 후에 저장한 데이터들을 다시 읽어올 때 영향을 주게 됨.
- blotName
- className
- tagName
이 세 가지는 명확하게 설정해줘야 함.
'프론트엔드 > React JS' 카테고리의 다른 글
[React JS] React-quill로 코드 블럭 에디터 설정하기(feat. highlight.js) (0) | 2024.02.16 |
---|---|
[React JS] React context API로 state 전역적으로 관리하기 (0) | 2024.01.06 |
인스타그램 API 이용하여 피드 긁어오기 (0) | 2024.01.02 |