/*
* @Author: ZhaoYing
* @Date: 2026-02-02 15:17:31
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-02 15:17:31
*/
/**
* RbMarkdown Component
*
* A comprehensive markdown renderer with support for:
* - Standard markdown syntax (headings, lists, tables, etc.)
* - Code syntax highlighting
* - Math equations (KaTeX)
* - Mermaid diagrams
* - ECharts visualizations
* - SVG rendering
* - Audio/video embedding
* - Interactive form elements
* - HTML comments visibility toggle
* - Editable mode with live preview
*
* @component
*/
import { useState, useRef, useEffect, type FC } from 'react'
import { Image, Input, Select, Form, Checkbox, Radio, ColorPicker, DatePicker, TimePicker, InputNumber, Slider } from 'antd'
import ReactMarkdown from 'react-markdown'
import RemarkGfm from 'remark-gfm'
import RemarkMath from 'remark-math'
import RemarkBreaks from 'remark-breaks'
import RehypeKatex from 'rehype-katex'
import RehypeRaw from 'rehype-raw'
import Code from './Code'
import VideoBlock from './VideoBlock'
import AudioBlock from './AudioBlock'
import Link from './Link'
import RbButton from './RbButton'
/** Props interface for RbMarkdown component */
interface RbMarkdownProps {
/** Markdown content to render */
content: string;
/** Whether to display HTML comments (default: false) */
showHtmlComments?: boolean;
/** Whether the content is editable (default: false) */
editable?: boolean;
/** Callback fired when content changes in edit mode */
onContentChange?: (content: string) => void;
/** Additional CSS classes */
className?: string;
}
/** Custom component mappings for markdown elements */
const components = {
h1: ({ children, ...props }: any) =>
{children}
,
h2: ({ children, ...props }: any) => {children}
,
h3: ({ children, ...props }: any) => {children}
,
h4: ({ children, ...props }: any) => {children}
,
h5: ({ children, ...props }: any) => {children}
,
h6: ({ children, ...props }: any) => {children}
,
ul: ({ children, ...props }: any) => ,
ol: ({ children, ...props }: any) => {children}
,
li: ({ children, ...props }: any) => {children},
blockquote: ({ children, ...props }: any) => {children}
,
p: ({ children, ...props }: any) => {children}
,
strong: ({ children, ...props }: any) => {children},
em: ({ children, ...props }: any) => {children},
del: ({ children, ...props }: any) => {children},
span: ({ children, style, ...restProps }: any) => {
// Apply special styling for HTML comment spans
if (style?.color === '#999') {
return {children}
}
return {children}
},
code: ({ children, className, ...props }: any) => ,
img: ({ src, alt, ...props }: any) => ,
video: ({ src, ...props }: any) => ,
audio: ({ src, ...props }: any) => ,
a: ({ href, children, ...props }: any) => {children},
button: ({ children }: any) => {[children]},
table: ({ children, ...props }: any) => ,
tr: ({ children, ...props }: any) => {children}
,
th: ({ children, ...props }: any) => {children} | ,
td: ({ children, ...props }: any) => {children} | ,
input: ({ children, ...props }: any) => {
switch (props.type) {
case 'color':
return
case 'time':
return
case 'date':
return
case 'datetime':
case 'datetime-local':
return
case 'week':
return
case 'month':
return
case 'number':
return
case 'search':
return
case 'range':
return
case 'submit':
case 'button':
return {[props.value || children]}
case 'checkbox':
return {children}
case 'password':
return
case 'radio':
return {children}
default:
return
}
},
select: ({ children, ...props }: any) => ,
textarea: ({ children, ...props }: any) => {children},
form: ({ children, ...props }: any) => ,
}
const RbMarkdown: FC = ({
content,
showHtmlComments = false,
editable = false,
onContentChange,
className
}) => {
const [editContent, setEditContent] = useState(content)
const textareaRef = useRef(null)
/** Sync edit content when external content changes */
useEffect(() => {
setEditContent(prev => prev !== content ? content : prev)
}, [content])
/** Handle textarea content changes and trigger callback */
const handleTextareaChange = (e: React.ChangeEvent) => {
const newContent = e.target.value
setEditContent(newContent)
/** Trigger real-time content change callback */
onContentChange?.(newContent)
}
/**
* Process content based on showHtmlComments flag
* Converts HTML comments to visible text when showHtmlComments is true
* Uses special span markup to display comments with styling
*/
const processedContent = showHtmlComments
? (editable ? editContent : content).replace(//g, (_match, commentContent) => {
/** Convert to styled text using span with html-comment class */
const escaped = commentContent.trim().replace(//g, '>')
return ``
})
: (editable ? editContent : content)
/** Render textarea in edit mode */
if (editable) {
return (
{/* Edit area with textarea */}
)
}
/** Handle keyboard shortcuts (e.g., Ctrl+C for copy) */
const handleKeyDown = (e: React.KeyboardEvent) => {
if ((e.ctrlKey || e.metaKey) && e.key === 'c') {
const selection = window.getSelection()
if (selection && selection.toString()) {
navigator.clipboard.writeText(selection.toString())
}
}
}
/** Render markdown preview mode */
return (
{
// return (tree) => {
// const iterate = (node: any) => {
// if (node.type === 'element' && !node.properties?.src && node.properties?.ref && node.properties.ref.startsWith('{') && node.properties.ref.endsWith('}'))
// delete node.properties.ref
// if (node.children)
// node.children.forEach(iterate)
// }
// tree.children.forEach(iterate)
// }
// },
]}
remarkPlugins={[RemarkGfm, RemarkMath, RemarkBreaks]}
remarkRehypeOptions={{
allowDangerousHtml: true,
}}
>
{processedContent}
)
}
export default RbMarkdown