diff --git a/public/uploads/1762049641372-u0moc5pbmuj.webp b/public/uploads/1762049641372-u0moc5pbmuj.webp new file mode 100644 index 0000000..f2119cb Binary files /dev/null and b/public/uploads/1762049641372-u0moc5pbmuj.webp differ diff --git a/public/uploads/1762049648293-vgw2mda8rx7.webp b/public/uploads/1762049648293-vgw2mda8rx7.webp new file mode 100644 index 0000000..07e49e3 Binary files /dev/null and b/public/uploads/1762049648293-vgw2mda8rx7.webp differ diff --git a/public/uploads/1762049842993-8hlvwqbkgo8.webp b/public/uploads/1762049842993-8hlvwqbkgo8.webp new file mode 100644 index 0000000..c083e6c Binary files /dev/null and b/public/uploads/1762049842993-8hlvwqbkgo8.webp differ diff --git a/public/uploads/1762049846758-2begnvv4hxd.webp b/public/uploads/1762049846758-2begnvv4hxd.webp new file mode 100644 index 0000000..6c20ff5 Binary files /dev/null and b/public/uploads/1762049846758-2begnvv4hxd.webp differ diff --git a/public/uploads/1762049851828-lvzzq326a1.webp b/public/uploads/1762049851828-lvzzq326a1.webp new file mode 100644 index 0000000..3c6d3e5 Binary files /dev/null and b/public/uploads/1762049851828-lvzzq326a1.webp differ diff --git a/src/app/components/Editor.tsx b/src/app/components/Editor.tsx index a23de18..59f81f7 100644 --- a/src/app/components/Editor.tsx +++ b/src/app/components/Editor.tsx @@ -44,7 +44,87 @@ export function Editor({ value, onChange, placeholder, withToolbar = true }: Pro const el = ref.current; if (!el) return; if (el.innerHTML !== value) el.innerHTML = value || ""; - }, [value]); + + // 이미지 리사이즈 및 삭제 기능 + const setupImageHandlers = () => { + const figures = el.querySelectorAll("figure[data-resizable='true']"); + figures.forEach((figure) => { + // 리사이즈 핸들 설정 + const handle = figure.querySelector(".resize-handle"); + const img = figure.querySelector("img"); + if (handle && img) { + // 기존 이벤트 리스너 제거 (중복 방지) + const newHandle = handle.cloneNode(true) as HTMLElement; + handle.parentNode?.replaceChild(newHandle, handle); + + let isResizing = false; + let startX = 0; + let startWidth = 0; + + const onMouseDown = (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + isResizing = true; + startX = e.clientX; + const rect = img.getBoundingClientRect(); + startWidth = rect.width; + document.addEventListener("mousemove", onMouseMove); + document.addEventListener("mouseup", onMouseUp); + }; + + const onMouseMove = (e: MouseEvent) => { + if (!isResizing) return; + const diffX = e.clientX - startX; + const scale = Math.max(0.1, Math.min(3, 1 + (diffX / startWidth))); + const newWidth = startWidth * scale; + img.style.width = `${newWidth}px`; + img.style.height = "auto"; + // onChange 동기화 + if (el) onChange(el.innerHTML); + }; + + const onMouseUp = () => { + isResizing = false; + document.removeEventListener("mousemove", onMouseMove); + document.removeEventListener("mouseup", onMouseUp); + }; + + newHandle.addEventListener("mousedown", onMouseDown); + } + + // 삭제 버튼 설정 + const deleteBtn = figure.querySelector(".delete-image-btn"); + if (deleteBtn) { + // 기존 이벤트 리스너 제거 (중복 방지) + const newDeleteBtn = deleteBtn.cloneNode(true) as HTMLElement; + deleteBtn.parentNode?.replaceChild(newDeleteBtn, deleteBtn); + + const onDeleteClick = (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (figure.parentNode) { + figure.parentNode.removeChild(figure); + // onChange 동기화 + if (el) onChange(el.innerHTML); + } + }; + + newDeleteBtn.addEventListener("click", onDeleteClick); + } + }); + }; + + // 초기 설정 및 변경 감지 + setupImageHandlers(); + const observer = new MutationObserver(() => { + setupImageHandlers(); + }); + observer.observe(el, { childList: true, subtree: true }); + + return () => { + observer.disconnect(); + }; + }, [value, onChange]); const insertHtmlAtCursor = useCallback((html: string) => { // eslint-disable-next-line deprecation/deprecation @@ -64,9 +144,7 @@ export function Editor({ value, onChange, placeholder, withToolbar = true }: Pro const r = await fetch("/api/uploads", { method: "POST", body: fd }); const data = await r.json(); if (!r.ok) throw new Error(JSON.stringify(data)); - const alt = window.prompt("이미지 대체텍스트(ALT)를 입력하세요.") || ""; - const caption = window.prompt("이미지 캡션(선택)") || ""; - const figure = `
${alt.replace(/
${caption.replace(/
`; + const figure = `
`; insertHtmlAtCursor(figure); // onChange 동기화 const el = ref.current; @@ -135,7 +213,7 @@ export function Editor({ value, onChange, placeholder, withToolbar = true }: Pro const toolbar = useMemo(() => { if (!withToolbar) return null; return ( -
+
@@ -163,10 +241,58 @@ export function Editor({ value, onChange, placeholder, withToolbar = true }: Pro - - - - + + + + @@ -196,8 +322,10 @@ export function Editor({ value, onChange, placeholder, withToolbar = true }: Pro border: "1px solid #ddd", borderRadius: 6, padding: 12, + overflow: "auto", }} suppressContentEditableWarning + className="[&_figure]:my-2 [&_figure]:block [&_figure_img]:max-w-full [&_figure_img]:h-auto [&_figure_img]:rounded-lg [&_figure_img]:border [&_figure_img]:border-neutral-200 [&_figure_img]:shadow-sm [&_figcaption]:mt-1 [&_figcaption]:text-xs [&_figcaption]:text-neutral-600 [&_figcaption]:text-center [&_img]:max-w-full [&_img]:h-auto [&_img]:rounded-lg [&_figure[data-resizable='true']]:hover_[.resize-handle]:opacity-100 [&_figure[data-resizable='true']_.resize-handle]:opacity-0 [&_figure[data-resizable='true']_.resize-handle]:transition-opacity [&_figure[data-resizable='true']]:hover_[.delete-image-btn]:opacity-100 [&_figure[data-resizable='true']_.delete-image-btn]:opacity-0 [&_figure[data-resizable='true']_.delete-image-btn]:transition-opacity" />
); diff --git a/src/app/posts/new/page.tsx b/src/app/posts/new/page.tsx index 8e17e83..d5e2a85 100644 --- a/src/app/posts/new/page.tsx +++ b/src/app/posts/new/page.tsx @@ -107,7 +107,10 @@ export default function NewPostPage() { 🙂 setForm((f) => ({ ...f, content: `${f.content}\n![image](${url})` }))} + onUploaded={(url) => { + const figure = `
`; + setForm((f) => ({ ...f, content: `${f.content}\n${figure}` })); + }} {...(boardSlug ? require("@/lib/photoPresets").getPhotoPresetBySlug(boardSlug) : {})} />