구글링을 아무리 해봐도 클라이언트단에서 s3로 바로 이미지를 업로드하는 방법이 없어서 직접 해결한 결과를 공유합니다.
<Editor
initialValue={content}
previewStyle="tab"
height="70vh"
initialEditType="markdown"
useCommandShortcut={true}
onChange={contentHandler}
ref={editorRef}
/>
우선 Toast Editor에 ref를 걸고,
const editorRef = useRef<Editor>(null);
로 정의해줍니다.
그 다음 useEffect로 editorRef에 걸려있는 addImageHook을 제거한 후 새로운 커스텀훅을 달아줍니다.
useEffect(() => {
if (editorRef.current) {
editorRef.current.getInstance().removeHook('addImageBlobHook');
editorRef.current
.getInstance()
.addHook('addImageBlobHook', (blob, callback) => {
const s3config = {
bucketName: process.env.REACT_APP_BUCKET_NAME as string,
region: process.env.REACT_APP_REGION as string,
accessKeyId: process.env.REACT_APP_ACCESS_ID as string,
secretAccessKey: process.env.REACT_APP_ACCESS_KEY as string,
};
const ReactS3Client = new S3(s3config);
ReactS3Client.uploadFile(blob, uuidv4())
.then((data) => callback(data.location, 'imageURL'))
.catch((err) => (window.location.href = '/error'));
});
}
}, []);
(s3라이브러리는 'react-aws-s3-typescript' 를 사용했습니다.)
위 코드대로 사용하시면 됩니다!
callback(data.location, 'imageURL') 은 업로드에 성공한 이미지의 URL주소를 담아 ![](주소)
형식으로 담아주는 함수를 의미합니다.
위에 과정을 따라하시다보면 Buffer가 없다는 에러가 나옵니다.
window.Buffer = window.Buffer || require('buffer').Buffer;
이 코드를 글로벌에 올려 주면 해결됩니다!
아래는 해당 컴포넌트 전체 코드입니다
import { useState, useRef, useEffect } from 'react';
import S3 from 'react-aws-s3-typescript';
import { v4 as uuidv4 } from 'uuid';
import { Editor } from '@toast-ui/react-editor';
import instance from 'utils/functions/axios';
import { PostData } from 'utils/functions/type';
import {
EditorContainer,
TitleUploadWrap,
EditorWrap,
Title,
UploadButton,
} from './styled';
window.Buffer = window.Buffer || require('buffer').Buffer;
type PostDataType = {
detailData?: PostData;
boardId: number;
};
export default function PostEditor({ detailData, boardId }: PostDataType) {
const [title, setTitle] = useState<string>('');
const [content, setContent] = useState<string>('');
const editorRef = useRef<Editor>(null);
const currentUrl = window.location.href;
const urlId = currentUrl.split('&postId=')[1];
useEffect(() => {
if (detailData) {
editorRef.current?.getInstance().setMarkdown(detailData.content);
setTitle(detailData.title);
setContent(detailData.content);
}
}, []);
useEffect(() => {
if (editorRef.current) {
editorRef.current.getInstance().removeHook('addImageBlobHook');
editorRef.current
.getInstance()
.addHook('addImageBlobHook', (blob, callback) => {
const s3config = {
bucketName: process.env.REACT_APP_BUCKET_NAME as string,
region: process.env.REACT_APP_REGION as string,
accessKeyId: process.env.REACT_APP_ACCESS_ID as string,
secretAccessKey: process.env.REACT_APP_ACCESS_KEY as string,
};
const ReactS3Client = new S3(s3config);
ReactS3Client.uploadFile(blob, uuidv4())
.then((data) => callback(data.location, 'imageURL'))
.catch((err) => (window.location.href = '/error'));
});
}
}, []);
const uploadHandler = () => {
if (window.location.pathname === '/writing') {
instance
.post(`/post?boardId=${boardId}`, { title: title, content: content })
.then(
(res) =>
(window.location.href = `/detail?boardId=${boardId}&postId=${res.data.id}`),
)
.catch((err) => console.log(err));
} else {
instance
.put(`/post?boardId=${boardId}&postId=${detailData?.id}`, {
title: title,
content: content,
})
.then(
() =>
(window.location.href = `/detail?boardId=${boardId}&postId=${urlId}`),
)
.catch((err) => console.log(err));
}
};
const titleHandler = (event: { target: { value: string } }) => {
setTitle(event.target.value);
};
const contentHandler = () => {
setContent(editorRef.current?.getInstance().getMarkdown() || '');
};
return (
<>
<EditorContainer>
<TitleUploadWrap>
<Title>
<input
onChange={titleHandler}
type="text"
placeholder="제목을 입력해 주세요."
value={title}
/>
</Title>
<UploadButton>
<button onClick={uploadHandler}>등록</button>
</UploadButton>
</TitleUploadWrap>
<EditorWrap>
<Editor
initialValue={content}
previewStyle="tab"
height="70vh"
initialEditType="markdown"
useCommandShortcut={true}
// hooks={{addImageBlobHook: }}
onChange={contentHandler}
ref={editorRef}
/>
</EditorWrap>
</EditorContainer>
</>
);
}