import React, { useState, useRef, useMemo } from 'react'; import CodeMirror from '@uiw/react-codemirror'; import { markdown, markdownLanguage } from '@codemirror/lang-markdown'; import { EditorView } from '@codemirror/view'; import MarkdownRenderer from './MarkdownRenderer'; import './MarkdownEditor.css'; const MarkdownEditor = ({ value, onChange, onImageUpload, placeholder = '在这里编写内容...', height = 400, showImageUpload = true }) => { const editorRef = useRef(null); const imageInputRef = useRef(null); const [showPreview, setShowPreview] = useState(false); const [showHeadingMenu, setShowHeadingMenu] = useState(false); // CodeMirror extensions const editorExtensions = useMemo(() => [ markdown({ base: markdownLanguage }), EditorView.lineWrapping, EditorView.theme({ "&": { fontSize: "14px", border: "2px solid #e2e8f0", borderRadius: "0 0 8px 8px", borderTop: "none", }, ".cm-content": { fontFamily: "'Monaco', 'Menlo', 'Consolas', monospace", padding: "1rem", minHeight: `${height}px`, }, ".cm-scroller": { fontFamily: "'Monaco', 'Menlo', 'Consolas', monospace", }, "&.cm-focused": { outline: "none", borderColor: "#667eea", boxShadow: "0 0 0 3px rgba(102, 126, 234, 0.1)", } }) ], [height]); // Markdown 插入函数 const insertMarkdown = (before, after = '', placeholder = '') => { if (!editorRef.current?.view) return; const view = editorRef.current.view; const selection = view.state.selection.main; const selectedText = view.state.doc.sliceString(selection.from, selection.to); const text = selectedText || placeholder; const newText = `${before}${text}${after}`; view.dispatch({ changes: { from: selection.from, to: selection.to, insert: newText }, selection: { anchor: selection.from + before.length, head: selection.from + before.length + text.length } }); view.focus(); }; // 工具栏操作 const toolbarActions = { bold: () => insertMarkdown('**', '**', '粗体文字'), italic: () => insertMarkdown('*', '*', '斜体文字'), heading: (level) => { setShowHeadingMenu(false); insertMarkdown('#'.repeat(level) + ' ', '', '标题'); }, quote: () => insertMarkdown('> ', '', '引用内容'), code: () => insertMarkdown('`', '`', '代码'), codeBlock: () => insertMarkdown('```\n', '\n```', '代码块'), link: () => insertMarkdown('[', '](url)', '链接文字'), unorderedList: () => insertMarkdown('- ', '', '列表项'), orderedList: () => insertMarkdown('1. ', '', '列表项'), table: () => { const tableTemplate = '\n| 列1 | 列2 | 列3 |\n| --- | --- | --- |\n| 单元格 | 单元格 | 单元格 |\n| 单元格 | 单元格 | 单元格 |\n'; insertMarkdown(tableTemplate, '', ''); }, hr: () => insertMarkdown('\n---\n', '', ''), image: () => imageInputRef.current?.click(), }; // 图片上传处理 const handleImageSelect = async (event) => { const file = event.target.files[0]; if (file && onImageUpload) { const imageUrl = await onImageUpload(file); if (imageUrl) { insertMarkdown(`![${file.name}](${imageUrl})`, '', ''); } } // Reset file input if (imageInputRef.current) { imageInputRef.current.value = ''; } }; return (
{/* 多级标题下拉菜单 */}
{showHeadingMenu && (
)}
{showImageUpload && ( )} {/* 预览按钮 */}
{showPreview ? ( ) : ( )} {showImageUpload && ( )}
); }; export default MarkdownEditor;