Pada kali ini saya akan sharing cara mengubah handler module image bawaan dari react-quill. Karena handler module image react-quill mengahasilkan data base64, yang mana datanya akan panjang sendiri untuk 1 image dan membuat memory membengkak saat data html tagnya akan disimpan ke database. Dan disini saya akan mengubah alur upload image dengan mengirim imagenya ke upload service yang disediakan BE untuk disimpan ke dalam local file system dan mengahasilkan response url string.
Kita mulai dengan menginstall react-quill
npm i react-quill
npm i quill
Berikut sample react-quill menggunakan handlers defaultnya
import ReactQuill from "react-quill";
import Quill from "quill";
import "react-quill/dist/quill.snow.css";
const RichContentEditor = () => {
const modules = React.useMemo(
() => ({
toolbar: {
container: [
[{ font: [] }],
[{ header: [2, 3, false] }],
[{ align: [] }],
["bold", "italic", "underline"],
[{ list: "ordered" }, { list: "bullet" }],
["link", "image", "video"],
["clean"],
],
},
history: {
delay: 500,
maxStack: 100,
userOnly: true,
},
}),
[]
);
return (
<>
<ReactQuill
id={"richContent"}
modules={modules}
formats={[
"header",
"font",
"bold",
"italic",
"underline",
"list",
"bullet",
"align",
"link",
"image",
"video",
]}
placeholder={""}
onChange={(e) => setContentValue(e)}
value={contentValue}
theme="snow"
/>
</>
);
}
dibawah ini contoh yang sudah menggunakan custom image handlersnya, untuk fungsi-fungsinya saya cantumkan dalam comment saja, saya hanya menjelaskan custom image handlers disini untuk membuka modal yang berisikan upload image dan form input property image yang dibutuhkan untuk dikirimkan ke upload service dan meng-embed url imagenya.
import React, { useRef, useState } from "react";
import ReactQuill from "react-quill";
import { Button, Form, Input, Modal } from "tunggal";
import { UploadPreview } from "pages/product/detail/detail.styles";
import Quill from "quill";
import "react-quill/dist/quill.snow.css";
import IPayloadAPI from "interfaces/iapi";
import upload from "libraries/api/upload";
import { callAPI } from "utils/fetchData";
import { errorNotification, formatBytes } from "utils/commonFunction";
import { ListStyled } from "pages/product/variant/variant.styles";
import { initialImage } from "helpers/constants";
import { default as UploadDrag } from "rc-upload";
import ImageBlot from "./image-blot";
const RichContentEditor = () => {
let reactQuillRef = useRef(null); // ReactQuill component
let quillRef = useRef(null); // Quill instance
const [showModalImage, setShowModalImage] = useState(false);
const [contentValue, setContentValue] = useState("");
const [isFetchingUpload, setIsFetchingUpload] = useState(false);
const [titleImage, setTitleImage] = useState("");
const [altImage, setAltImage] = useState("");
const [selectedImage, setSelectedImage] = useState(initialImage);
// show / close modal
const handleShowModal = () => {
setShowModalImage(!showModalImage);
handleReset();
};
const handleReset = () => {
setAltImage("");
setTitleImage("");
setSelectedImage(initialImage);
};
// function onchange property image
const onChangeInput = (key: string, e: string) => {
const valueRegex = e.replace(/[^\w\s]/gi, "");
if (key === "title") {
setTitleImage(valueRegex);
} else {
setAltImage(valueRegex);
}
};
// simpan fileImage ke dalam state
const onUpload = async (e) => {
const file = e;
if (file) {
setSelectedImage({
image: file,
size: file.size,
fileName: file.name,
});
}
e.target.value = null;
};
// function cek fokus index terakhir element input react-quill
const handleSetQuillRef = () => {
if (typeof reactQuillRef.current.getEditor !== "function") {
return;
}
const temp = reactQuillRef.current.getEditor();
quillRef.current = temp;
};
// function Request ke upload Service yang menghasilkan url image
const uploadImage = async () => {
if (selectedImage.image) {
handleSetQuillRef();
const range = quillRef.current.getSelection(true);
const titles = titleImage ? titleImage : selectedImage.fileName;
const fileData = new FormData();
fileData.append("file", selectedImage.image);
fileData.append("type", "richcontent");
fileData.append("uploadType", "image");
fileData.append("filename", titles);
let params: IPayloadAPI = upload.uploadFile(fileData);
setIsFetchingUpload(true);
try {
const response = await callAPI(params);
const { data } = response.data;
// proses mengembed image pada range index terakhir
quillRef.current.insertEmbed(
range.index,
"image",
{
alt: altImage, // untuk memasukkan property ALT image
title: titles, // untuk memasukkan property title image
url: data.original, // untuk memasukkan properti src image
},
"user"
);
set fokus element input lompat 2 index agar tidak bertabrakan dengan image yang sudah di embed
quillRef.current.setSelection(range.index + 2, "silent");
quillRef.current.focus();
handleShowModal();
} catch (error) {
errorNotification(`Upload File`, error);
}
}
};
// Penggunaan useMemo disini untuk memoize hasil function custom handlers yang dipanggil berulang kali agar tidak bentrok
const modules = React.useMemo(
() => ({
toolbar: {
container: [
[{ font: [] }],
[{ header: [2, 3, false] }],
[{ align: [] }],
["bold", "italic", "underline"],
[{ list: "ordered" }, { list: "bullet" }],
["link", "image", "video"],
["clean"],
],
},
history: {
delay: 500,
maxStack: 100,
userOnly: true,
},
handlers: {
image: handleShowModal, // customs handlers image untuk memanggil function handleShowModal
},
}),
[]
);
// daftarkan Quill instance module image
Quill.register({ "formats/imageBlot": ImageBlot });
return (
<>
<ReactQuill
id={"richContent"}
modules={modules}
formats={[
"header",
"font",
"bold",
"italic",
"underline",
"list",
"bullet",
"align",
"link",
"image",
"video",
]}
placeholder={""}
onChange={(e) => setContentValue(e)}
value={contentValue}
theme="snow"
/>
<Modal
size="sm"
show={showModalImage}
onClose={handleShowModal}
title="Tambah Image"
variant="clean"
>
<Modal.Body>
<Form>
<Form.Item
label="Upload Image"
extra={
<>
Atau seret file ke area di atas <br />
Format .jpg or .jpeg
</>
}
>
<UploadPreview>
<UploadDrag
name="upload"
beforeUpload={(e) => onUpload(e)}
accept="image/jpeg"
type="drag"
>
<Button icon="plus">Upload Image</Button>
</UploadDrag>
</UploadPreview>
{selectedImage.image && (
<ListStyled isModal>
<li>
<div>
<strong>{selectedImage.fileName}</strong>
<span>{formatBytes(selectedImage.size)}</span>
</div>
<Button
iconOnly
icon="trash"
onClick={() => setSelectedImage(initialImage)}
/>
</li>
</ListStyled>
)}
</Form.Item>
<Form.Item label="Image Title">
<Input
placeholder="Input Image Title"
value={titleImage}
onChange={(e) => onChangeInput("title", e)}
/>
</Form.Item>
<Form.Item label="Image ALT Title">
<Input
placeholder="Input Image ALT Title"
value={altImage}
onChange={(e) => onChangeInput("alt", e)}
/>
</Form.Item>
</Form>
</Modal.Body>
<Modal.Footer>
<Button disabled={isFetchingUpload} onClick={handleShowModal}>
Cancel
</Button>
<Button
color="primary"
loading={isFetchingUpload}
onClick={uploadImage}
>
Upload
</Button>
</Modal.Footer>
</Modal>
</>
);
};
export default RichContentEditor;
Buat File image.blot.tsx untuk mendaftarkan custom property image (alt, title, src)
import Quill from 'quill';
const BlockEmbed = Quill.import('formats/image');
class ImageBlot extends BlockEmbed {
static create(val) {
const node = super.create();
node.setAttribute('alt', val.alt);
node.setAttribute('title', val.title);
node.setAttribute('src', val.url);
return node;
}
static value(node) {
return {
alt: node.getAttribute('alt'),
title: node.getAttribute('title'),
url: node.getAttribute('src'),
};
}
}
ImageBlot.blotName = 'image';
ImageBlot.tagName = 'img';
export default ImageBlot;
seperti ini output yang dihasilkan
Top comments (0)