From d7887f319712c6fe2b3af4c71a9f318980991cab Mon Sep 17 00:00:00 2001 From: Choi hee ruk Date: Tue, 14 Oct 2025 00:33:56 +0900 Subject: [PATCH 01/10] =?UTF-8?q?=E2=9C=A8=20Feat:=20Quill=20=EC=97=90?= =?UTF-8?q?=EB=94=94=ED=84=B0=20=ED=8F=B0=ED=8A=B8=20=EC=A0=81=EC=9A=A9=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=ED=88=B4?= =?UTF-8?q?=EB=B0=94=20=EC=98=B5=EC=85=98=20=ED=99=95=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/TextEditor.jsx | 32 +++++++++++++++------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/components/common/TextEditor.jsx b/src/components/common/TextEditor.jsx index adfb7141..392fe16e 100644 --- a/src/components/common/TextEditor.jsx +++ b/src/components/common/TextEditor.jsx @@ -183,29 +183,35 @@ const useChecklistToolbarManager = (reactQuillRef) => { const TextEditor = ({ value, onChange, onFontChange, font }) => { const reactQuillRef = useRef(null); - const lastSelectionFontRef = useRef(null); // 마지막 선택 영역의 폰트 추적 + const lastSelectionFontRef = useRef(null); + + const handleChange = (content) => { + onChange(content); + }; useFontPersistence(reactQuillRef); useChecklistToolbarManager(reactQuillRef); - // 외부 Dropdown에서 선택된 폰트 → 에디터 전체에 적용 + // 외부 Dropdown에서 선택된 폰트 → 선택된 영역에만 적용 (수정됨!) useEffect(() => { const quill = reactQuillRef.current?.getEditor(); if (!quill || !font) { return; } - // 전체 텍스트 범위 가져오기 - const length = quill.getLength(); - if (length <= 1) { - // 텍스트가 없을 때는 커서 위치에 폰트 설정 + const range = quill.getSelection(); + + if (!range) { + // 포커스가 없으면 다음 입력에 적용될 포맷만 설정 + quill.format('font', font, 'api'); + } else if (range.length === 0) { + // 커서만 있고 선택 영역이 없으면 다음 입력에 적용 quill.format('font', font, 'api'); } else { - // 텍스트가 있을 때는 전체에 폰트 적용 - quill.formatText(0, length, { font }, 'api'); + // 선택 영역이 있으면 해당 영역에만 폰트 적용 + quill.formatText(range.index, range.length, { font }, 'api'); } - // 외부에서 폰트가 변경되면 ref도 업데이트 lastSelectionFontRef.current = font; }, [font]); @@ -217,12 +223,10 @@ const TextEditor = ({ value, onChange, onFontChange, font }) => { } const handleTextChange = (delta, oldDelta, source) => { - // 'user' 소스는 사용자 입력, 'api' 소스는 프로그래밍 방식 변경 if (source !== 'user') { return; } - // 폰트 변경이 포함된 경우만 체크 const hasFontChange = delta.ops?.some((op) => op.attributes?.font); if (!hasFontChange) { return; @@ -260,16 +264,13 @@ const TextEditor = ({ value, onChange, onFontChange, font }) => { .trim(); const hasContent = () => { - // 텍스트가 있는지 확인 if (getVisibleText().length > 0) { return true; } - // 리스트 요소가 있는지 확인 const hasLists = root.querySelector('ol, ul'); if (hasLists) { return true; } - // 이미지나 다른 블록 요소가 있는지 확인 const hasBlocks = root.querySelector('img, iframe, video'); if (hasBlocks) { return true; @@ -312,6 +313,7 @@ const TextEditor = ({ value, onChange, onFontChange, font }) => { toolbar: { container: [ [{ font: Font.whitelist }], + [{ size: ['small', false, 'large', 'huge'] }], ['bold', 'italic', 'underline', 'strike'], [{ align: [] }], [{ list: 'ordered' }, { list: 'bullet' }, { list: 'check' }], @@ -339,7 +341,7 @@ const TextEditor = ({ value, onChange, onFontChange, font }) => { ref={reactQuillRef} theme="snow" value={value || ''} - onChange={onChange} + onChange={handleChange} modules={modules} placeholder="여기에 내용을 입력해주세요." /> From a3e4e7b96cbde684338306816824258cad13ae01 Mon Sep 17 00:00:00 2001 From: Choi hee ruk Date: Tue, 14 Oct 2025 00:36:29 +0900 Subject: [PATCH 02/10] =?UTF-8?q?=F0=9F=8E=A8=20Style:=20=EB=AA=A8?= =?UTF-8?q?=EB=8B=AC=20=EC=97=B4=EB=A6=BC/=EB=8B=AB=ED=9E=98=20=EC=8B=9C?= =?UTF-8?q?=20=EB=B6=80=EB=93=9C=EB=9F=AC=EC=9A=B4=20=EC=A0=84=ED=99=98=20?= =?UTF-8?q?=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/modal/Modal.jsx | 30 ++++++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/components/common/modal/Modal.jsx b/src/components/common/modal/Modal.jsx index aaf10c83..e09e063f 100644 --- a/src/components/common/modal/Modal.jsx +++ b/src/components/common/modal/Modal.jsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import Button from '@/components/common/button/Button'; import ModalHeader from '@/components/common/modal/ModalHeader'; import PostContent from '@/components/common/modal/PostContent'; @@ -14,6 +14,18 @@ const Modal = ({ createdAt, font, }) => { + const [visible, setVisible] = useState(false); + + useEffect(() => { + if (isOpen) { + setVisible(true); + } else { + // 닫힐 때 애니메이션 고려해 약간의 지연 + const timer = setTimeout(() => setVisible(false), 200); + return () => clearTimeout(timer); + } + }, [isOpen]); + useEffect(() => { document.body.style.overflow = isOpen ? 'hidden' : ''; return () => (document.body.style.overflow = ''); @@ -32,20 +44,24 @@ const Modal = ({ return () => document.removeEventListener('keydown', handleKeyDown); }, [isOpen, onClose]); - if (!isOpen) { + // 완전히 닫힌 상태에서는 Portal 자체 제거 + if (!visible) { return null; } return (