"use client" import { useCallback, useEffect, useState } from "react" import { createPortal } from "react-dom" import { cn } from "@/lib/utils" import { htmlToMarkdown, markdownToHtml } from "./convert" import { HtmlEditor } from "./html-editor" import { MarkdownEditor } from "./markdown-editor" import type { ContentMode, ContentValue, UploadImageHandler } from "./types" type ContentEditorProps = { value: ContentValue onChange: (next: ContentValue) => void placeholder?: string disabled?: boolean onUploadImage?: UploadImageHandler height?: number | string } function normalizeHeight(height?: number | string) { if (typeof height === "number") { return `${height}px` } if (typeof height === "string" && height.trim()) { return height } return "400px" } function getModeLabel(mode: ContentMode) { return mode === "markdown" ? "Markdown" : "HTML" } function convertContent(mode: ContentMode, raw: string) { if (mode === "markdown") { return markdownToHtml(raw) } return htmlToMarkdown(raw) } export function ContentEditor({ value, onChange, placeholder, disabled = false, onUploadImage, height, }: ContentEditorProps) { const editorHeight = normalizeHeight(height) const [fullscreen, setFullscreen] = useState(false) const [mounted, setMounted] = useState(false) useEffect(() => { setMounted(true) }, []) useEffect(() => { if (!fullscreen) { return } const previousOverflow = document.body.style.overflow document.body.style.overflow = "hidden" const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape") { setFullscreen(false) } } window.addEventListener("keydown", handleKeyDown) return () => { document.body.style.overflow = previousOverflow window.removeEventListener("keydown", handleKeyDown) } }, [fullscreen]) const handleModeChange = useCallback( (nextMode: ContentMode) => { if (disabled || nextMode === value.mode) { return } const currentText = value.raw.trim() if (!currentText) { onChange({ mode: nextMode, raw: "" }) return } const confirmed = window.confirm( `切换到 ${getModeLabel(nextMode)} 模式会尝试自动转换内容,复杂格式可能有损。是否继续?` ) if (!confirmed) { return } onChange({ mode: nextMode, raw: convertContent(value.mode, value.raw), }) }, [disabled, onChange, value.mode, value.raw] ) const content = (