From fc9433b6f09dc9daf2a77249ee215cdfe11aea49 Mon Sep 17 00:00:00 2001 From: publdaze Date: Sat, 4 May 2024 21:55:35 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat=20:=20Toast=20UI=EC=97=90=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=82=BD=EC=9E=85=20=EC=8B=9C=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=EC=97=90=20=EC=A0=80=EC=9E=A5=ED=95=9C=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EB=9D=84=EC=9B=8C=EC=A3=BC=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 서버에 저장한 이미지 url 받아와 처리 - #454 --- src/api/postApi.ts | 17 +++++++++++++++++ src/components/Editor/StandardEditor.tsx | 21 +++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/api/postApi.ts b/src/api/postApi.ts index 905c57370..09af036c6 100644 --- a/src/api/postApi.ts +++ b/src/api/postApi.ts @@ -39,6 +39,22 @@ const useUploadPostMutation = () => { return useMutation(fetcher); }; +const useUploadPostImageMutation = () => { + const fetcher = async ({ file }: { file: Blob }) => { + const formData = new FormData(); + formData.append('file', file); + + const { data } = await axios.post('/posts/files', formData, { + headers: { + 'content-type': 'multipart/form-data', + }, + }); + return data; + }; + + return useMutation(fetcher); +}; + const useGetPostListQuery = ({ categoryId, searchType, search, page, size }: BoardSearch) => { const fetcher = () => axios.get('/posts', { params: { categoryId, searchType, search, page, size } }).then(({ data }) => data); @@ -251,6 +267,7 @@ const useGetMemberTempPostsQuery = ({ page, size = 10 }: PageAndSize) => { export { useUploadPostMutation, + useUploadPostImageMutation, useGetPostListQuery, useGetRecentPostsQuery, useGetTrendPostsQuery, diff --git a/src/components/Editor/StandardEditor.tsx b/src/components/Editor/StandardEditor.tsx index ff45e05b0..57dea4ff7 100644 --- a/src/components/Editor/StandardEditor.tsx +++ b/src/components/Editor/StandardEditor.tsx @@ -1,9 +1,12 @@ import React from 'react'; import { useMediaQuery, useTheme } from '@mui/material'; +import { HookMap } from '@toast-ui/editor'; import { Editor, EditorProps } from '@toast-ui/react-editor'; import '@toast-ui/editor/dist/toastui-editor.css'; import '@toast-ui/editor/dist/theme/toastui-editor-dark.css'; +import { useUploadPostImageMutation } from '@api/postApi'; +import { getServerImgUrl } from '@utils/converter'; interface StandardEditorProps extends EditorProps { forwardedRef?: React.MutableRefObject; @@ -13,11 +16,29 @@ const StandardEditor = ({ forwardedRef, ...props }: StandardEditorProps) => { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('sm')); + const { mutate: uploadPostImageMutation } = useUploadPostImageMutation(); + + const handleImageUpload: HookMap['addImageBlobHook'] = (blob, callback) => { + // TODO: 이미지 크기가 30MB 넘어가면 에러 처리 + // TODO: 서버에서 이미지 받아오는 동안 딜레이 처리 + uploadPostImageMutation( + { file: blob }, + { + onSuccess: ({ filePath }) => { + callback(getServerImgUrl(filePath)); + }, + }, + ); + }; + return ( Date: Sat, 18 May 2024 20:24:29 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat=20:=20=EC=84=9C=EB=B2=84=EC=97=90?= =?UTF-8?q?=EC=84=9C=20TUI=20Editor=EC=97=90=20=EC=97=85=EB=A1=9C=EB=93=9C?= =?UTF-8?q?=ED=95=9C=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EB=B0=9B=EC=95=84?= =?UTF-8?q?=EC=98=A4=EB=8A=94=20=EB=8F=99=EC=95=88=20=EB=A1=9C=EB=94=A9=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 로딩중 문구 삽입하기 - 로딩중 문구의 시작위치, 끝위치 파악 후 받아온 경로 이미지 마크다운으로 교체하기 - #454 --- src/components/Editor/StandardEditor.tsx | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/components/Editor/StandardEditor.tsx b/src/components/Editor/StandardEditor.tsx index 57dea4ff7..f26baef85 100644 --- a/src/components/Editor/StandardEditor.tsx +++ b/src/components/Editor/StandardEditor.tsx @@ -18,14 +18,26 @@ const StandardEditor = ({ forwardedRef, ...props }: StandardEditorProps) => { const { mutate: uploadPostImageMutation } = useUploadPostImageMutation(); - const handleImageUpload: HookMap['addImageBlobHook'] = (blob, callback) => { + const handleImageUpload: HookMap['addImageBlobHook'] = (blob) => { // TODO: 이미지 크기가 30MB 넘어가면 에러 처리 - // TODO: 서버에서 이미지 받아오는 동안 딜레이 처리 + + const editor = forwardedRef?.current.getInstance(); + if (!editor) return; + + const [startPos] = editor.getSelection(); + + const IMAGE_MARKDOWN_LOADING_MSG = `![Uploading image...]()`; + editor.insertText(IMAGE_MARKDOWN_LOADING_MSG); + + // selection 타입을 명확히 하여 마크다운 위치 계산 + const [startLinePos, startCharPos] = startPos as Exclude; + const endPos = [startLinePos, startCharPos + IMAGE_MARKDOWN_LOADING_MSG.length] as Exclude; + uploadPostImageMutation( { file: blob }, { - onSuccess: ({ filePath }) => { - callback(getServerImgUrl(filePath)); + onSuccess: ({ fileName, filePath }) => { + editor.replaceSelection(`![${fileName}](${getServerImgUrl(filePath)})`, startPos, endPos); }, }, ); From 923c005e7fc661e0f3b36c595ccaa23ea09f444a Mon Sep 17 00:00:00 2001 From: publdaze Date: Sat, 18 May 2024 20:49:26 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat=20:=20TUI=20Editor=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #454 --- src/components/Editor/StandardEditor.tsx | 10 ++++++++-- src/constants/apiResponseMessage.ts | 7 +++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/components/Editor/StandardEditor.tsx b/src/components/Editor/StandardEditor.tsx index f26baef85..87c7b916f 100644 --- a/src/components/Editor/StandardEditor.tsx +++ b/src/components/Editor/StandardEditor.tsx @@ -1,12 +1,14 @@ import React from 'react'; +import toast from 'react-hot-toast'; import { useMediaQuery, useTheme } from '@mui/material'; import { HookMap } from '@toast-ui/editor'; import { Editor, EditorProps } from '@toast-ui/react-editor'; +import { useUploadPostImageMutation } from '@api/postApi'; +import { FILE } from '@constants/apiResponseMessage'; +import { getServerImgUrl } from '@utils/converter'; import '@toast-ui/editor/dist/toastui-editor.css'; import '@toast-ui/editor/dist/theme/toastui-editor-dark.css'; -import { useUploadPostImageMutation } from '@api/postApi'; -import { getServerImgUrl } from '@utils/converter'; interface StandardEditorProps extends EditorProps { forwardedRef?: React.MutableRefObject; @@ -39,6 +41,10 @@ const StandardEditor = ({ forwardedRef, ...props }: StandardEditorProps) => { onSuccess: ({ fileName, filePath }) => { editor.replaceSelection(`![${fileName}](${getServerImgUrl(filePath)})`, startPos, endPos); }, + onError: () => { + editor.deleteSelection(startPos, endPos); + toast.error(FILE.error.uploadFail); + }, }, ); }; diff --git a/src/constants/apiResponseMessage.ts b/src/constants/apiResponseMessage.ts index 9453302c4..69e52f910 100644 --- a/src/constants/apiResponseMessage.ts +++ b/src/constants/apiResponseMessage.ts @@ -55,3 +55,10 @@ export const STUDENT_ID = { existing: '이미 존재하는 학번입니다.', }, }; + +export const FILE = { + success: {}, + error: { + uploadFail: '업로드가 실패하였습니다.', + }, +} as const; From 4faf78a6b8c092ef415bd91a403198d19c62861a Mon Sep 17 00:00:00 2001 From: publdaze Date: Sat, 18 May 2024 20:51:12 +0900 Subject: [PATCH 4/7] =?UTF-8?q?refactor=20:=20=EC=83=81=EC=88=98=EB=A1=9C?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8A=94=20=EA=B0=9D=EC=B2=B4?= =?UTF-8?q?=EC=97=90=20as=20const=20=EB=B6=99=EC=9D=B4=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #846 --- src/constants/apiResponseMessage.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/constants/apiResponseMessage.ts b/src/constants/apiResponseMessage.ts index 69e52f910..97df2d235 100644 --- a/src/constants/apiResponseMessage.ts +++ b/src/constants/apiResponseMessage.ts @@ -47,14 +47,14 @@ export const LOGIN_ID = { error: { existing: '이미 존재하는 아이디입니다.', }, -}; +} as const; export const STUDENT_ID = { success: {}, error: { existing: '이미 존재하는 학번입니다.', }, -}; +} as const; export const FILE = { success: {}, From 66e328f1967bc6bf3dffbb845ae75f2e9baa7f0b Mon Sep 17 00:00:00 2001 From: publdaze Date: Sat, 18 May 2024 21:39:55 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat=20:=20TUI=20Editor=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=82=AC?= =?UTF-8?q?=EC=9D=B4=EC=A6=88=20=EC=A0=9C=ED=95=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #454 --- src/components/Editor/StandardEditor.tsx | 11 +++++++++-- src/constants/apiResponseMessage.ts | 4 ++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/Editor/StandardEditor.tsx b/src/components/Editor/StandardEditor.tsx index 87c7b916f..912492777 100644 --- a/src/components/Editor/StandardEditor.tsx +++ b/src/components/Editor/StandardEditor.tsx @@ -4,7 +4,7 @@ import { useMediaQuery, useTheme } from '@mui/material'; import { HookMap } from '@toast-ui/editor'; import { Editor, EditorProps } from '@toast-ui/react-editor'; import { useUploadPostImageMutation } from '@api/postApi'; -import { FILE } from '@constants/apiResponseMessage'; +import { FILE, MAX_FILE_SIZE } from '@constants/apiResponseMessage'; import { getServerImgUrl } from '@utils/converter'; import '@toast-ui/editor/dist/toastui-editor.css'; @@ -21,7 +21,14 @@ const StandardEditor = ({ forwardedRef, ...props }: StandardEditorProps) => { const { mutate: uploadPostImageMutation } = useUploadPostImageMutation(); const handleImageUpload: HookMap['addImageBlobHook'] = (blob) => { - // TODO: 이미지 크기가 30MB 넘어가면 에러 처리 + if (blob.size > MAX_FILE_SIZE) { + toast.error(FILE.error.exceedFileSize, { + style: { + maxWidth: 1500, + }, + }); + return; + } const editor = forwardedRef?.current.getInstance(); if (!editor) return; diff --git a/src/constants/apiResponseMessage.ts b/src/constants/apiResponseMessage.ts index 97df2d235..5988a92d1 100644 --- a/src/constants/apiResponseMessage.ts +++ b/src/constants/apiResponseMessage.ts @@ -1,3 +1,5 @@ +import { formatFileSize } from '@utils/converter'; + export const COMMON = {} as const; export const PASSWORD = { @@ -56,9 +58,11 @@ export const STUDENT_ID = { }, } as const; +export const MAX_FILE_SIZE = 30000000; // Byte export const FILE = { success: {}, error: { uploadFail: '업로드가 실패하였습니다.', + exceedFileSize: `파일이 제한된 크기(${formatFileSize(MAX_FILE_SIZE)})를 초과하였습니다.`, }, } as const; From bbd121f5b465ac832a988bbeb28d91d4c808fe5d Mon Sep 17 00:00:00 2001 From: publdaze Date: Sat, 18 May 2024 23:06:01 +0900 Subject: [PATCH 6/7] =?UTF-8?q?feat=20:=20=EB=A1=9C=EB=94=A9=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EC=B2=98=EB=A6=AC=20=ED=9B=84=20=EA=B0=9C?= =?UTF-8?q?=ED=96=89=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #454 --- src/components/Editor/StandardEditor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Editor/StandardEditor.tsx b/src/components/Editor/StandardEditor.tsx index 912492777..c40cab208 100644 --- a/src/components/Editor/StandardEditor.tsx +++ b/src/components/Editor/StandardEditor.tsx @@ -36,7 +36,7 @@ const StandardEditor = ({ forwardedRef, ...props }: StandardEditorProps) => { const [startPos] = editor.getSelection(); const IMAGE_MARKDOWN_LOADING_MSG = `![Uploading image...]()`; - editor.insertText(IMAGE_MARKDOWN_LOADING_MSG); + editor.insertText(`${IMAGE_MARKDOWN_LOADING_MSG}\n`); // selection 타입을 명확히 하여 마크다운 위치 계산 const [startLinePos, startCharPos] = startPos as Exclude; From 2208ecef93a8453c5f54aa28a31c7689deb434c0 Mon Sep 17 00:00:00 2001 From: publdaze Date: Sat, 18 May 2024 23:06:36 +0900 Subject: [PATCH 7/7] =?UTF-8?q?fix=20:=20=EC=82=AC=EC=9D=B4=EC=A6=88=2030M?= =?UTF-8?q?B=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #454 --- src/constants/apiResponseMessage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/constants/apiResponseMessage.ts b/src/constants/apiResponseMessage.ts index 5988a92d1..e05fd888d 100644 --- a/src/constants/apiResponseMessage.ts +++ b/src/constants/apiResponseMessage.ts @@ -58,7 +58,7 @@ export const STUDENT_ID = { }, } as const; -export const MAX_FILE_SIZE = 30000000; // Byte +export const MAX_FILE_SIZE = 30 * 1024 * 1024; // Byte export const FILE = { success: {}, error: {