728x90
๋ฐ์ํ
๐ก RichTextEditor์์ ์ด๋ฏธ์ง ์ฌ์ฉ ์ ๋ด๋ถ์ base64๋ก ์ด๋ฏธ์ง๊ฐ ์ฌ๋ผ๊ฐ๊ฒ ๋๋๋ฐ ์ด๋ ๋๋ฌด ๊ธด ๋ฌธ์์ด์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ์ด๋ฏธ์ง๊ฐ ์ฌ๋ฌ์ฅ ์ฌ๋ผ๊ฐ๋ค๋ฉด ๋ ํฐ ๋ฌธ์ ๋ฅผ ์ผ๊ธฐํ ๊ฒ์ด๋ค. ๊ทธ๋ฌ๋ฏ๋ก ์ด๋ฏธ์ง๋ฅผ ๋จผ์ ์๋ฒ์ ์ฌ๋ฆฌ๊ณ ๊ทธ ์ฃผ์๋ฅผ ๋ฐํ๋ฐ์ ์ด๋ฏธ์ง ํ๊ทธ๋ก ์ฌ์ฉํ๋ ๋ฐฉ์์ ์ฑํํ๋ค.
react-quill
The Quill rich-text editor as a React component.. Latest version: 2.0.0, last published: 2 years ago. Start using react-quill in your project by running `npm i react-quill`. There are 806 other projects in the npm registry using react-quill.
www.npmjs.com
Image Upload with ReactQuill
ReactQuill์ modules : { handlers : { image : imageHandler }} ์ต์ ์ผ๋ก ์ด๋ฏธ์ง ์ ๋ก๋๋ฅผ ํธ๋ค๋ง ํ ์ ์๋๋ฐ ์ด๋ฏธ์ง ํ์ผ์ ๋จผ์ ์๋ฒ์ ์ ์ฅ ์์ฒญ์ ํ๊ณ ๋ฐํ๋ฐ์ ์ฃผ์๋ก ์๋ํฐ์ ํ์ฌ ์ปค์์ ์ฝ์ ํ๋ ๋ฐฉ์์ ๋๋ค.
// ์ด๋ฏธ์ง ์
๋ก๋ ์์ฒญ API
const imageApi = async (img: File): Promise<APIUploadType> => {
const formData = new FormData();
formData.append('files', img);
return client.post('board/upload', formData);
};
// ์ด๋ฏธ์ง ํธ๋ค๋ฌ ํจ์
const imageHandler = () => {
// ๋น fileInput์ผ๋ก ์ด๋ฏธ์ง๋ฅผ ๋ฐ๋๋ค.
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.click();
input.addEventListener('change', async () => {
if (input.files === null) {
DC.alert('ํ์ผ์ด ์ค๋น๋์ง ์์์ต๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์.');
return;
}
if (quillRef.current === null) {
DC.alert('ํ
์คํธ ์์ฑ๊ธฐ ์ค๋ฅ์
๋๋ค. ์๋ก๊ณ ์นจ ํด์ฃผ์ธ์.');
return;
}
const file = input.files[0];
try {
//์ด๋ฏธ์ง ์
๋ก๋ API ์์ฒญ
const res: APIUploadType = await imageApi(file);
const imgUrl = '์๋ฒ URL' + res.fileImg.split('/image/')[1];
const editor = quillRef.current.getEditor();
const range = editor.getSelection();
if (range === null) {
DC.alert('ํ
์คํธ ์์ฑ๊ธฐ ์ค๋ฅ์
๋๋ค. ์๋ก๊ณ ์นจ ํด์ฃผ์ธ์.');
return;
}
// ์๋ํฐ์ ๋ฐํ๋ฐ์ ์ฃผ์ ๊ฐ์ ์ด๋ฏธ์ง ์ฝ์
editor.insertEmbed(range.index, 'image', imgUrl);
if (fileImg === '') {
setFileImg(imgUrl);
}
// ์ปค์๋ฅผ ์ด๋ฏธ์ง ๋ค๋ก ์ด๋
editor.setSelection({ ...range, index: range.index + 1 });
} catch (error) {
console.log(error);
}
});
}
์ ์ฒด ์ฝ๋
interface QuillStateType {
content: string;
setContent: React.Dispatch<React.SetStateAction<string>>;
fileImg: string;
setFileImg: (fileImg: string) => void;
}
function QuillCustom({ content, setContent, fileImg, setFileImg }: QuillStateType) {
const quillRef = useRef(null) as RefObject<ReactQuill>;
const imageApi = async (img: File): Promise<APIUploadType> => {
const formData = new FormData();
formData.append('files', img);
return client.post('board/upload', formData);
};
const imageHandler = () => {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.click();
input.addEventListener('change', async () => {
if (input.files === null) {
DC.alert('ํ์ผ์ด ์ค๋น๋์ง ์์์ต๋๋ค. ๋ค์ ์๋ํด ์ฃผ์ธ์.');
return;
}
if (quillRef.current === null) {
DC.alert('ํ
์คํธ ์์ฑ๊ธฐ ์ค๋ฅ์
๋๋ค. ์๋ก๊ณ ์นจ ํด์ฃผ์ธ์.');
return;
}
const file = input.files[0];
try {
//์ด๋ฏธ์ง ์
๋ก๋ API
const res: APIUploadType = await imageApi(file);
console.log(res);
const imgUrl = '์๋ฒ URL' + res.fileImg.split('/image/')[1];
const editor = quillRef.current.getEditor();
const range = editor.getSelection();
if (range === null) {
DC.alert('ํ
์คํธ ์์ฑ๊ธฐ ์ค๋ฅ์
๋๋ค. ์๋ก๊ณ ์นจ ํด์ฃผ์ธ์.');
return;
}
editor.insertEmbed(range.index, 'image', imgUrl);
if (fileImg === '') {
setFileImg(imgUrl);
}
editor.setSelection({ ...range, index: range.index + 1 });
} catch (error) {
console.log(error);
}
});
};
const modules = useMemo(
() => ({
toolbar: {
container: [
[{ header: '1' }, { header: '2' }, { font: [] }],
[{ size: [] }],
['bold', 'italic', 'underline', 'strike', 'blockquote'],
[{ list: 'ordered' }, { list: 'bullet' }, { indent: '-1' }, { indent: '+1' }],
['link', 'image'],
['clean'],
],
handlers: { image: imageHandler },
},
clipboard: {
matchVisual: false,
},
}),
[],
);
const formats = [
'header','font','size','bold','italic','underline','strike','blockquote',
'list','bullet','indent','link','image','video',
];
return (
<ReactQuill
ref={quillRef}
value={content}
onChange={setContent}
modules={modules}
formats={formats}
theme="snow"
/>
);
}
export default QuillCustom;
728x90
๋ฐ์ํ
'React > TypeScript' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
React Typescript Interface Tuple Generic (0) | 2024.03.25 |
---|---|
React TypeScript useInfiniteQuery Infinite Scroll (0) | 2024.03.15 |
React TypeScript Zustand Infinite Scroll with IntersectionObserver (0) | 2024.03.14 |
React Axios TypeScript with JWT Boilerplate (0) | 2024.03.14 |
React TypeSciprt - Community Project (0) | 2024.03.13 |