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
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>
|
|
);
|
|
}
|