| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- 'use client';
- import '../style.scss';
- import { useState, useRef, useEffect } from 'react';
- import { useAuthContext } from '@/contexts/authProvider';
- import { fetchCommentUpdate } from '@/lib/api/forum/comment';
- import BoardResponse from '@/dtos/response/forum/board/boardResponse';
- import PostResponse from '@/dtos/response/forum/post/postResponse';
- import CommentUpdateRequest from '@/dtos/request/forum/comment/commentUpdateRequest';
- import { type CommentItem } from '@/types/forum/comment';
- import EmojiPicker from '@/app/component/EmojiPicker';
- import { BoardLayout, CommentConst } from '@/constants/forum';
- import { throwError } from '@/lib/utils/server';
- import Loading from '@/app/component/Loading';
- type Props = {
- board: BoardResponse,
- post: PostResponse,
- comment: CommentItem,
- onSuccess: () => void;
- onCancel?: () => void;
- };
- export default function EditForm({ board, post, comment, onSuccess, onCancel }: Props)
- {
- const { isAuthenticated } = useAuthContext();
- const boardMeta = board.boardMeta;
- const [error, setError] = useState<string|null>(null);
- const [loading, setLoading] = useState<boolean>(false);
- const [form, setForm] = useState<CommentUpdateRequest>({
- postID: post.id,
- commentID: comment.id,
- mention: comment.mention?.rawHandle ?? '',
- content: comment.content,
- isSecret: false
- });
- const textareaRef = useRef<HTMLTextAreaElement>(null);
- const contentRef = useRef<HTMLTextAreaElement>(null);
- const editorRef = useRef<any>(null);
- // 입력 창 높이 조절
- const resizeTextarea = () => {
- const textarea = textareaRef.current;
- if (textarea) {
- textarea.style.height = 'auto'; // 초기화
- textarea.style.height = `${textarea.scrollHeight}px`; // 높이 조정
- }
- };
- useEffect(() => {
- if (error) {
- alert(error);
- setError(null);
- }
- }, [error]);
- useEffect(() => {
- resizeTextarea();
- }, [form.content]);
- // 입력 내용 저장
- const handleChange = (key: keyof CommentUpdateRequest, value: any) => {
- setForm(prev => ({ ...prev, [key]: value }));
- };
- // 이모지 선택
- const handleEmoji = (emoji: string) => {
- handleChange("content", (form.content + emoji));
- };
- // Ctrl + Enter 최종 제출
- const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
- if (e.ctrlKey && e.key === 'Enter') {
- e.preventDefault();
- handleSubmit();
- }
- };
- // 댓글+답글 수정
- const handleSubmit = async () => {
- if (!isAuthenticated) {
- return setError('로그인 후 이용해주세요.');
- }
- if (!form.content.trim()) {
- return;
- }
- try {
- if (!form.content) {
- if (boardMeta.list.layout === BoardLayout.QnA) {
- editorRef.current!.editorInstance?.editing.view.focus();
- } else {
- contentRef.current!.focus();
- }
- throw new Error('내용을 입력해주세요.');
- } else if (!boardMeta.comment.enableEditor && !contentRef.current) {
- // 기본 textarea 사용 시 글자 수 검사
- if (form.content.length > CommentConst.MaxAllowedContentLength) {
- contentRef.current!.focus();
- throw new Error(`내용은 ${CommentConst.MaxAllowedContentLength}자 이내로 작성해주세요.`);
- }
- }
- const formData = new FormData();
- formData.append('postID', String(form.postID));
- formData.append('commentID', String(form.commentID));
- formData.append('isSecret', String(form.isSecret));
- if (form.content) {
- const doc = new DOMParser().parseFromString(form.content, 'text/html');
- doc.querySelectorAll('img[src]').forEach(img => {
- img.setAttribute('src', 'data:image/');
- });
- formData.append('content', doc.body.innerHTML);
- }
- // 이미지 정보
- if (boardMeta.list.layout === BoardLayout.QnA) {
- editorRef.current?.getImageStore().forEach((i: any) => {
- if (i.image?.size > 0 && i.name) {
- formData.append('images', i.image, i.name);
- }
- });
- // 미디어 정보
- editorRef.current!.getMediaStore().forEach((m: any) => {
- if (m.url) {
- formData.append('medias', m.url);
- }
- });
- // 첨부 파일
- editorRef.current!.getFileStore().forEach((f: any) => {
- if (f?.size > 0 && f.name) {
- formData.append('files', f.file, f.name);
- }
- });
- }
- const res = await fetchCommentUpdate(formData);
- await throwError(res);
- onSuccess();
- } catch (err: any) {
- setError(err.message);
- } finally {
- setLoading(false);
- resetForm();
- }
- };
- // 입력 내용 초기화
- const resetForm = () => {
- setForm({
- postID: post.id,
- commentID: comment.id,
- mention: '',
- content: '',
- isSecret: false
- });
- if (contentRef.current) {
- contentRef.current.value = '';
- }
- if (textareaRef.current) {
- textareaRef.current.value = '';
- }
- resizeTextarea();
- };
- // 취소
- const handleCancel = () => {
- if (typeof onCancel === 'function') {
- onCancel();
- } else {
- resetForm();
- }
- };
- return (
- <>
- {loading ? (
- <Loading type={2} />
- ) : (
- <article className='edit-form'>
- <div>
- <textarea
- name='comment'
- rows={1}
- value={form.content}
- title='댓글 입력 창'
- placeholder=''
- ref={textareaRef}
- onChange={(e) => handleChange("content", e.target.value)}
- onKeyDown={handleKeyDown}
- disabled={loading}
- />
- </div>
- <div>
- {/* 비밀글, 이모지 */}
- <EmojiPicker onEmojiSelect={handleEmoji} />
- </div>
- <div>
- <button type='button' className='btn btn-default' onClick={handleCancel}>취소</button>
- <button type='button' className='btn btn-submit' onClick={handleSubmit}>확인</button>
- </div>
- </article>
- )}
- </>
- )
- };
|