✉️ 기술 스택
React, TypeScript, Zustand, Tiptap, DOMPurify, tailwindCSS, Flowbite
Tiptap Editor
Tiptap Rich Text Editor - the Headless WYSIWYG Editor
✔️ 쉽게 확장 가능
✔️ ProseMirror 기반: 구조적 편집 가능
✔️ 확장 기능 커스터마이징 가능
✔️ 공식 문서 설명이 잘 되어 있어서 따라하기 편함
커스터마이징 에디터 구성
✏️ 에디터 설정
- 필요한 extension (StarterKit, Underline, Highlight) 선택적으로 추가
- prose 기반 스타일링 적용
- debounce 적용으로 입력 최적화
const editor = useEditor({
editorProps: {
attributes: {
class:
'prose prose-sm dark:prose-invert prose-headings:my-2 prose-p:m-0 min-h-[120px] focus:outline-none',
},
},
extensions: [StarterKit, Underline, Highlight],
content,
onUpdate({ editor }) {
debouncedOnChange(editor.getHTML());
},
});
✏️ 툴바 기능
- Format: Paragraph, Heading 1, Heading 2
- Bold, Italic, Underline, Highlight
{/* Bold */}
<button
type="button"
onClick={() => editor.chain().focus().toggleBold().run()}
className={`...`}
>
<BoldIcon />
<span className="sr-only">Bold</span>
</button>
HTML 렌더링
작성된 HTML 콘텐츠는 <p><strong>텍스트</strong></p>
형식으로 저장됨
이를 안전하게 보여주기 위해 DOMPurify.sanitize
적용
const photoTalkMessage = DOMPurify.sanitize(photoTalk.message);
<p dangerouslySetInnerHTML={{ __html: photoTalkMessage }} />
dangerouslySetInnerHTML
: WYSIWYG 작성 콘텐츠를 그대로 표현DOMPurify.sanitize
: script injection과 같은 보안 문제 방지
에디터 구조 및 확장 적용
에디터를 다른 영역에도 재사용 가능하도록 설계했으며, 교통 안내 입력 등에서도 동일한 에디터 UI 활용
📦components
┣ 📂common
┃ ┣ 📂Editor
┃ ┃ ┣ 📜MenuBar.tsx
┃ ┃ ┗ 📜TiptapEditor.tsx
// TiptapEditor.tsx
return (
<div className="border rounded-md bg-white dark:bg-gray-800">
{editor && (
<>
<MenuBar editor={editor} />
<div className="p-4 h-[160px] overflow-y-auto">
<EditorContent editor={editor} />
</div>
</>
)}
</div>
);
// PhotoTalkEditor.tsx
import TipTapEditor from '@/components/common/Editor/TiptapEditor';
<TipTapEditor
content={form.message}
onChange={(value) => {
setForm((prev) => ({ ...prev, message: value }));
}}
/>
// TransportationItem.tsx
import TipTapEditor from '@/components/common/Editor/TiptapEditor';
<TipTapEditor
content={transportationInputs[inputKey] || ''}
onChange={(value) => {
updateTransportationInput(inputKey, value);
}}
/>