"use client"; import { useEditor, EditorContent } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; import Image from "@tiptap/extension-image"; import Link from "@tiptap/extension-link"; import Placeholder from "@tiptap/extension-placeholder"; import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight"; import { common, createLowlight } from "lowlight"; import { Toggle } from "@/components/ui/toggle"; import dynamic from "next/dynamic"; import { Bold, Italic, Strikethrough, Code, Heading1, Heading2, Heading3, Quote, List, ListOrdered, Link as LinkIcon, ImageIcon, CodeSquare, Minus, Undo2, Redo2, Maximize2, Minimize2, FileText, } from "lucide-react"; const MarkdownEditor = dynamic(() => import("./MarkdownEditor"), { ssr: false }); const lowlight = createLowlight(common); interface RichEditorProps { value: string; onChange: (html: string) => void; placeholder?: string; isFullscreen?: boolean; isMarkdown?: boolean; onToggleFullscreen?: () => void; onSwitchToMarkdown?: () => void; } export default function RichEditor({ value, onChange, placeholder = "开始写文章...", isFullscreen, isMarkdown, onToggleFullscreen, onSwitchToMarkdown, }: RichEditorProps) { const editor = useEditor({ extensions: [ StarterKit.configure({ codeBlock: false, }), Image.configure({ HTMLAttributes: { class: "rounded-lg my-4" }, }), Link.configure({ openOnClick: false, HTMLAttributes: { class: "text-primary underline underline-offset-2", }, }), Placeholder.configure({ placeholder }), CodeBlockLowlight.configure({ lowlight }), ], content: value, onUpdate: ({ editor }) => { onChange(editor.getHTML()); }, editorProps: { attributes: { class: "prose-literary min-h-[300px] px-4 py-3 focus:outline-none", }, }, }); if (!editor) return null; function addLink() { const url = window.prompt("输入链接地址:"); if (url) { editor .chain() .focus() .extendMarkRange("link") .setLink({ href: url }) .run(); } } function addImage() { const url = window.prompt("输入图片地址:"); if (url) { editor.chain().focus().setImage({ src: url }).run(); } } const isFs = !!isFullscreen; return (
{/* 工具栏 — 始终显示 */}
{/* 富文本格式按钮 — Markdown 模式下禁用 */} editor.chain().focus().toggleHeading({ level: 1 }).run()} disabled={!!isMarkdown} aria-label="标题 1" > editor.chain().focus().toggleHeading({ level: 2 }).run()} disabled={!!isMarkdown} aria-label="标题 2" > editor.chain().focus().toggleHeading({ level: 3 }).run()} disabled={!!isMarkdown} aria-label="标题 3" >
editor.chain().focus().toggleBold().run()} disabled={!!isMarkdown} aria-label="粗体" > editor.chain().focus().toggleItalic().run()} disabled={!!isMarkdown} aria-label="斜体" > editor.chain().focus().toggleStrike().run()} disabled={!!isMarkdown} aria-label="删除线" > editor.chain().focus().toggleCode().run()} disabled={!!isMarkdown} aria-label="行内代码" >
editor.chain().focus().toggleBlockquote().run()} disabled={!!isMarkdown} aria-label="引用" > editor.chain().focus().toggleBulletList().run()} disabled={!!isMarkdown} aria-label="无序列表" > editor.chain().focus().toggleOrderedList().run()} disabled={!!isMarkdown} aria-label="有序列表" >
editor.chain().focus().toggleCodeBlock().run()} disabled={!!isMarkdown} aria-label="代码块" > editor.chain().focus().setHorizontalRule().run()} disabled={!!isMarkdown} aria-label="分割线" >
editor.chain().focus().undo().run()} disabled={!!isMarkdown || !editor.can().undo()} aria-label="撤销" > editor.chain().focus().redo().run()} disabled={!!isMarkdown || !editor.can().redo()} aria-label="重做" >
{/* Markdown 切换 */} {onSwitchToMarkdown && ( )} {/* 全屏切换 */} {onToggleFullscreen && ( {isFullscreen ? : } )}
{/* 内容区域 — 根据模式切换 */}
{isMarkdown ? ( ) : ( )}
); }