You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

66 lines
2.2 KiB
TypeScript

// frontend/components/tasks/RichNotes.tsx — contentEditable 리치 노트
"use client";
import { useEffect, useRef } from "react";
import { Icon } from "@/components/Icon";
import type { IconName } from "@/components/icons/paths";
type Tool = { cmd: string; val?: string; icon?: IconName; txt?: string; title?: string };
const NOTE_TOOLS: Tool[] = [
{ cmd: "formatBlock", val: "h3", txt: "제목" },
{ cmd: "bold", icon: "bold", title: "굵게" },
{ cmd: "italic", icon: "italic", title: "기울임" },
{ cmd: "insertUnorderedList", icon: "list", title: "글머리 목록" },
{ cmd: "insertOrderedList", icon: "hash", title: "번호 목록" },
{ cmd: "formatBlock", val: "blockquote", icon: "quote", title: "인용" },
];
export function RichNotes({
taskId,
value,
onChange,
}: {
taskId: string;
value: string;
onChange: (html: string) => void;
}) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (ref.current) ref.current.innerHTML = value || "";
// taskId 변경 시에만 외부값으로 리셋 (편집 중 덮어쓰기 방지)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [taskId]);
const exec = (cmd: string, val?: string) => {
ref.current?.focus();
// execCommand: 프로토타입과 동일(현재 deprecated이나 MVP 한정 사용)
document.execCommand(cmd, false, val);
if (ref.current) onChange(ref.current.innerHTML);
};
return (
<div className="rich">
<div className="rich-tools">
{NOTE_TOOLS.map((t, i) => (
<button
key={i}
className="rt-btn"
title={t.title || t.txt}
onMouseDown={(e) => {
e.preventDefault();
exec(t.cmd, t.val);
}}
>
{t.icon ? <Icon name={t.icon} /> : <span className="rt-txt">{t.txt}</span>}
</button>
))}
</div>
<div
className="rich-edit"
ref={ref}
contentEditable
suppressContentEditableWarning
data-ph="자유롭게 메모하세요 — 제목, 목록, 인용을 섞어 쓸 수 있어요."
onInput={() => ref.current && onChange(ref.current.innerHTML)}
/>
</div>
);
}