diff --git a/gitmon-client/src/app/[id]/[repo]/[slug]/update/UpdatePost.tsx b/gitmon-client/src/app/[id]/[repo]/[slug]/update/UpdatePost.tsx index f521154..48a23af 100644 --- a/gitmon-client/src/app/[id]/[repo]/[slug]/update/UpdatePost.tsx +++ b/gitmon-client/src/app/[id]/[repo]/[slug]/update/UpdatePost.tsx @@ -99,7 +99,7 @@ export default function UpdatePost({ token, post }: UpdatePostProps) { return (
- +
) } diff --git a/gitmon-client/src/app/create-post/AddPost.tsx b/gitmon-client/src/app/create-post/AddPost.tsx index 04863ae..68164c3 100644 --- a/gitmon-client/src/app/create-post/AddPost.tsx +++ b/gitmon-client/src/app/create-post/AddPost.tsx @@ -91,7 +91,7 @@ export default function AddPost({ token }: { token: string | null }) { return (
- +
) } diff --git a/gitmon-client/src/components/Post/PostForm.tsx b/gitmon-client/src/components/Post/PostForm.tsx index 9621840..f6e71e9 100644 --- a/gitmon-client/src/components/Post/PostForm.tsx +++ b/gitmon-client/src/components/Post/PostForm.tsx @@ -9,6 +9,8 @@ import { Card } from '@components/ui/card' import MarkdownPreview from './markdown-preview' import ToolbarButton from './toolbar-button' import ImageUploader from './image-uploader' +import { uploadImage } from '@lib/upload' +import { toast } from 'sonner' import { Bold, Italic, @@ -31,18 +33,24 @@ import { Post } from '@lib/types' interface PostFormProps { onPostSaved: (post: { title: string; content: string }) => void post: Omit | null + token?: string | null } -export function PostForm({ onPostSaved, post }: PostFormProps) { +export function PostForm({ onPostSaved, post, token }: PostFormProps) { const params = useParams() const [title, setTitle] = useState(post?.title || '') const [content, setContent] = useState(post?.content || '') const [showImageUploader, setShowImageUploader] = useState(false) + const [isUploading, setIsUploading] = useState(false) const textareaRef = useRef(null) const insertTextAtCursor = useCallback( (textBefore: string, textAfter = '') => { - if (!textareaRef.current) return + if (!textareaRef.current) { + // Textarea가 언마운트된 경우(예: Preview 탭): 본문 끝에 추가 + setContent(prev => prev + textBefore + textAfter) + return + } const textarea = textareaRef.current const start = textarea.selectionStart @@ -106,10 +114,53 @@ export function PostForm({ onPostSaved, post }: PostFormProps) { } const handleImageInsert = (imageUrl: string) => { - insertTextAtCursor(`![Image](${imageUrl})`) + insertTextAtCursor(`![](${imageUrl})`) setShowImageUploader(false) } + const handleFilesToUpload = async (files: FileList | File[]) => { + if (!files || files.length === 0) return + const fileArray = Array.from(files) + const imageFile = fileArray.find(f => f.type.startsWith('image/')) + if (!imageFile) return + if (!token) { + toast('로그인이 필요하거나 토큰이 없습니다.') + return + } + try { + setIsUploading(true) + const url = await uploadImage(imageFile, token) + insertTextAtCursor(`![](${url})`) + } catch (e: unknown) { + toast((e as Error)?.message || '이미지 업로드에 실패했습니다.') + } finally { + setIsUploading(false) + } + } + + const onDragOver = (e: React.DragEvent) => { + e.preventDefault() + } + + const onDrop = async (e: React.DragEvent) => { + e.preventDefault() + const dt = e.dataTransfer + if (dt?.files && dt.files.length > 0) { + await handleFilesToUpload(dt.files) + } + } + + const onPaste = async (e: React.ClipboardEvent) => { + const items = e.clipboardData?.files + if (items && items.length > 0) { + const hasImage = Array.from(items).some(f => f.type.startsWith('image/')) + if (hasImage) { + e.preventDefault() + await handleFilesToUpload(items) + } + } + } + return (
@@ -187,9 +238,15 @@ export function PostForm({ onPostSaved, post }: PostFormProps) {