게시판 기능 구현중 아래처럼 이미지가 포함되어있으면 글과 댓글수 사이에 이미지포함여부를 표시하는 기능을 구현하려고 했는데 검색해도 나오지 않아서
직접 구현했다. 추후에 나같이 검색하는 사람들을 위해 조금이나마 정보를 공유해보려고 한다.
글을 작성할때 Toast ui react Editor 라이브러리를 사용하는데 이미지를 추가하면
위처럼 담기게 된다.
해당 돔에 접근 후 담겨 있는 텍스트중에 ![]()
와 같은 문구가 있으면 이미지가 있다고 판단해야하나?라고 생각을 했었는데, 그렇게 하면 수 많은 사이드 이펙트들이 따라올 것 같았다. 그래서 해당 돔에 접근해서 노드리스트 중 이미지태그가 있을경우 이미지가 포함되어 있다고 표시하는게 좋을 것 같다는 생각이 들어서 아래와 같이 코드를 작성하였다.
문제는 useEffect를 매번 불러온다는 것.. 사실 글 내용이 바꼇을때만 실행하면 되는데 해당 코드를 작성할땐 기능구현에 급급해서 최적화는 신경쓰지 않았던 것 같다. 수정해야겠다!
위 코드를 설명하자면, 구조가 조금은 복잡한데,
editorContentNode로 'toastui-editor-contents' 클래스를 가진 돔에 접근 후 [0]번째 요소를 지정한다.
0번째 리스트의 자식들중 0번째의 자식노드리스트드를 저장한 후 forEach로 반복문을 돌아 사용한 태그들의 목록을 arr라는 새로운 배열에 담는다.
(기능구현에 급급해서 arr라고 해놨다. 나중에 시멘틱한 이름으로 꼭 변경해야겠다..)
위 처럼 <img>
태그가 담겨 있다면 setIsImage를 1로 바꿔주고 아니라면 0으로 바꿔주면 해결된다.
글이 너무 장황해서ㅠㅠ 이해를 돕기위해 해당 컴포넌트의 전체 코드를 아래에 붙여넣습니다!
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 [isImage, setIsImage] = useState<number>(0);
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(() => {
const editorContentNode = document.getElementsByClassName(
'toastui-editor-contents',
)[0];
let contentChildNodes = editorContentNode.childNodes[0]?.childNodes;
let arr: string[] = [];
contentChildNodes?.forEach((el) => {
arr.push(el.nodeName);
});
if (arr.includes('IMG')) {
setIsImage(1);
} else {
setIsImage(0);
}
});
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(() => (window.location.href = '/error'));
});
}
}, []);
const uploadHandler = () => {
if (window.location.pathname === '/writing') {
instance
.post(`/post?boardId=${boardId}`, {
title: title,
content: content,
isImage: isImage,
})
.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,
isImage: isImage,
})
.then(
() =>
(window.location.href = `/detail?boardId=${boardId}&postId=${urlId}`),
)
.catch((err) => console.log(err));
}
};
const titleHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
setTitle(event.target.value);
};
const contentHandler = () => {
setContent(editorRef.current?.getInstance().getMarkdown() || '');
};
return (
<>
<EditorContainer>
<TitleUploadWrap>
<Title>
<input
onChange={titleHandler}
type="text"
placeholder="제목을 입력해 주세요."
value={title}
maxLength={24}
/>
</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>
</>
);
}