docs: add comments to the src/components directory

This commit is contained in:
zhaoying
2026-02-02 16:14:39 +08:00
parent 9a38e8a4a0
commit a191e32f71
55 changed files with 1417 additions and 375 deletions

View File

@@ -1,15 +1,31 @@
import { memo } from 'react'
/*
* @Author: ZhaoYing
* @Date: 2026-02-02 15:14:59
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-02 15:14:59
*/
/**
* AudioBlock Component
*
* Renders audio elements from markdown nodes.
* Extracts audio source URLs and creates HTML audio players with controls.
*
* @component
*/
import type { FC } from 'react'
import { memo, type FC } from 'react'
/** Props interface for AudioBlock component */
interface AudioBlockProps {
node: {
children: { properties: { src: string } }[]
}
}
/** Audio block component that renders audio elements from markdown nodes */
const AudioBlock: FC<AudioBlockProps> = (props) => {
// console.log('AudioBlock', props)
const { children } = props.node;
/** Extract audio source URLs from node children and filter out empty values */
const srcs = children.map(item => item.properties?.src).filter(item => item)
return (

View File

@@ -1,22 +1,45 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-02 15:15:05
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-02 15:15:05
*/
/**
* Code Component
*
* A versatile code rendering component that supports:
* - Syntax-highlighted code blocks
* - ECharts visualizations
* - SVG rendering
* - Mermaid diagrams
* - Inline code snippets
*
* @component
*/
import { type FC, useMemo } from 'react'
import SyntaxHighlighter from 'react-syntax-highlighter';
import { atelierHeathLight } from 'react-syntax-highlighter/dist/esm/styles/hljs';
import CopyBtn from './CopyBtn';
import ReactEcharts from 'echarts-for-react';
import CopyBtn from './CopyBtn';
import Svg from './Svg'
import MermaidChart from './MermaidChart'
/** Props interface for Code component */
type ICodeProps = {
children: string;
className: string;
}
/** Code block component that renders syntax-highlighted code or special visualizations */
const Code: FC<ICodeProps> = (props) => {
const { children, className } = props;
/** Extract language from className (e.g., 'language-javascript' -> 'javascript') */
const language = className?.split('-')[1]
console.log('Code', props)
// Parse ECharts configuration from code content
const charData = useMemo(() => {
if (language !== 'echarts') return null;
try {
@@ -27,6 +50,7 @@ const Code: FC<ICodeProps> = (props) => {
}
}, [language, children])
// Render ECharts visualization
if (language === 'echarts') {
return (
<ReactEcharts
@@ -39,6 +63,7 @@ const Code: FC<ICodeProps> = (props) => {
)
}
// Render SVG content
if (language === 'svg') {
return (
<Svg
@@ -46,6 +71,7 @@ const Code: FC<ICodeProps> = (props) => {
/>
)
}
// Render Mermaid diagram
if (language === 'mermaid') {
return (
<MermaidChart
@@ -54,6 +80,7 @@ const Code: FC<ICodeProps> = (props) => {
)
}
// Render syntax-highlighted code block with copy button
if (className) {
return (
<div className="rb:relative">
@@ -81,6 +108,7 @@ const Code: FC<ICodeProps> = (props) => {
</div>
)
}
// Render inline code
return <code className="rb:bg-[#F0F3F8] rb:px-1 rb:py-0.5 rb:rounded rb:text-sm rb:font-mono rb:whitespace-break-spaces">{children}</code>
}

View File

@@ -1,9 +1,27 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-02 15:15:11
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-02 15:15:11
*/
/**
* CodeBlock Component
*
* A standalone code block component for displaying formatted code with:
* - Syntax highlighting
* - Optional copy functionality
* - Configurable size and line numbers
*
* @component
*/
import { type FC } from 'react'
import SyntaxHighlighter from 'react-syntax-highlighter';
import { atelierHeathLight } from 'react-syntax-highlighter/dist/esm/styles/hljs';
import CopyBtn from './CopyBtn';
/** Props interface for CodeBlock component */
type ICodeBlockProps = {
value: string;
needCopy?: boolean;
@@ -11,12 +29,7 @@ type ICodeBlockProps = {
showLineNumbers?: boolean;
}
// enum languageType {
// echarts = 'echarts',
// mermaid = 'mermaid',
// svg = 'svg',
// }
/** Code block component for displaying formatted code with optional copy functionality */
const CodeBlock: FC<ICodeBlockProps> = ({
value,
needCopy = true,

View File

@@ -1,15 +1,31 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-02 15:15:21
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-02 15:15:21
*/
/**
* CopyBtn Component
*
* A button component that copies text to clipboard and displays a success message.
* Uses the copy-to-clipboard library for cross-browser compatibility.
*
* @component
*/
import { type FC } from 'react'
import { useTranslation } from 'react-i18next'
import copy from 'copy-to-clipboard'
import { Button, App } from 'antd'
/** Props interface for CopyBtn component */
type ICopyBtnProps = {
value: string;
className?: string;
style?: React.CSSProperties;
}
/** Copy button component that copies text to clipboard and shows success message */
const CopyBtn: FC<ICopyBtnProps> = ({
value,
className,
@@ -18,6 +34,7 @@ const CopyBtn: FC<ICopyBtnProps> = ({
const { t } = useTranslation()
const { message } = App.useApp()
/** Copy value to clipboard and show success message */
const handleCopy = () => {
copy(value)
message.success(t('common.copySuccess'))

View File

@@ -1,13 +1,29 @@
import { memo } from 'react'
/*
* @Author: ZhaoYing
* @Date: 2026-02-02 15:15:55
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-02 15:15:55
*/
/**
* Link Component
*
* A secure link component that opens URLs in a new tab.
* Includes security attributes (noopener, noreferrer) to prevent security vulnerabilities.
*
* @component
*/
import { memo } from 'react'
import type { FC, ReactNode } from 'react'
/** Props interface for Link component */
interface LinkProps {
href: string;
children: ReactNode;
}
/** Link component that opens in a new tab with security attributes */
const Link: FC<LinkProps> = (props) => {
// console.log('Link', props)
const { children, href } = props;
return <a href={href} target="_blank" rel="noopener noreferrer">{children}</a>
}

View File

@@ -1,8 +1,26 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-02 15:16:01
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-02 15:16:01
*/
/**
* MermaidChart Component
*
* Renders Mermaid diagrams as images.
* - Converts Mermaid syntax to SVG
* - Converts SVG to base64 data URL for display
* - Generates unique IDs based on content hash
*
* @component
*/
import { useRef, useEffect, useState, type FC } from 'react'
import mermaid from 'mermaid'
import CryptoJS from 'crypto-js'
import { Image } from 'antd'
/** Initialize Mermaid with default configuration */
mermaid.initialize({
startOnLoad: true,
theme: 'default',
@@ -12,6 +30,7 @@ mermaid.initialize({
},
})
/** Convert SVG string to base64 data URL for image display */
const svgToBase64 = (svgGraph: string) => {
const svgBytes = new TextEncoder().encode(svgGraph)
const blob = new Blob([svgBytes], { type: 'image/svg+xml;charset=utf-8' })
@@ -22,8 +41,11 @@ const svgToBase64 = (svgGraph: string) => {
reader.readAsDataURL(blob)
})
}
/** Mermaid chart component that renders Mermaid diagrams as images */
const MermaidChart: FC<{ content: string }> = ({ content }) => {
const [chartSvg, setChartSvg] = useState<string>('')
/** Generate unique ID based on content hash to avoid conflicts */
const id = useRef(`mermaidchart_${CryptoJS.MD5(content).toString()}`)
useEffect(() => {
@@ -33,6 +55,7 @@ const MermaidChart: FC<{ content: string }> = ({ content }) => {
drawDiagram()
}, [content])
/** Render Mermaid diagram and convert to base64 image */
const drawDiagram = async function () {
const { svg } = await mermaid.render(id.current, content);

View File

@@ -1,15 +1,30 @@
import { memo } from 'react'
/*
* @Author: ZhaoYing
* @Date: 2026-02-02 15:16:06
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-02 15:16:06
*/
/**
* Paragraph Component
*
* A simple paragraph component for rendering markdown paragraphs.
*
* @component
*/
import { memo } from 'react'
import type { FC, ReactNode } from 'react'
/** Props interface for Paragraph component */
interface ParagraphProps {
node: {
children: ReactNode;
};
children: string[]
}
/** Paragraph component for rendering markdown paragraphs */
const Paragraph: FC<ParagraphProps> = (props) => {
// console.log('Paragraph', props)
const { children } = props
return <p>{children}</p>

View File

@@ -1,15 +1,32 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-02 15:16:10
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-02 15:16:10
*/
/**
* RbButton Component
*
* A button component for rendering buttons in markdown content.
* Wraps Ant Design Button component.
*
* @component
*/
import { memo } from 'react'
import type { FC, ReactNode } from 'react'
import { Button } from 'antd'
/** Props interface for RbButton component */
interface RbButtonProps {
node: {
children: ReactNode;
};
children: string[]
}
/** Button component for rendering buttons in markdown */
const RbButton: FC<RbButtonProps> = (props) => {
console.log('RbButton', props)
const { children } = props;
return (

View File

@@ -1,15 +1,28 @@
/*
* @Author: ZhaoYing
* @Date: 2026-02-02 15:16:14
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-02 15:16:14
*/
/**
* Svg Component
*
* Renders SVG content from string using dangerouslySetInnerHTML.
* Used for displaying SVG code blocks in markdown.
*
* @component
*/
import * as React from 'react';
/** Props interface for Svg component */
interface SvgProps {
content: string;
}
/**
* 渲染SVG内容的组件
*/
/** Component for rendering SVG content from string */
function Svg(props: SvgProps): JSX.Element {
const { content } = props;
// console.log('Svg', props)
return React.createElement(
'div',

View File

@@ -1,15 +1,32 @@
import { memo } from 'react'
/*
* @Author: ZhaoYing
* @Date: 2026-02-02 15:16:18
* @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-02 15:54:55
*/
/**
* VideoBlock Component
*
* Renders video elements from markdown nodes.
* Extracts video source URLs and creates HTML video players with controls.
*
* @component
*/
import { memo } from 'react'
import type { FC } from 'react'
/** Props interface for VideoBlock component */
interface VideoBlockProps {
node: {
children: { properties: { src: string } }[]
}
}
/** Video block component that renders video elements from markdown nodes */
const VideoBlock: FC<VideoBlockProps> = (props) => {
// console.log('VideoBlock', props)
const { children } = props.node;
/** Extract video source URLs from node children and filter out empty values */
const srcs = children.map(item => item.properties?.src).filter(item => item)
return (

View File

@@ -1,3 +1,28 @@
/*
* @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'
@@ -5,8 +30,6 @@ import RemarkMath from 'remark-math'
import RemarkBreaks from 'remark-breaks'
import RehypeKatex from 'rehype-katex'
import RehypeRaw from 'rehype-raw'
import type { FC } from 'react'
import { useState, useRef, useEffect } from 'react'
import Code from './Code'
import VideoBlock from './VideoBlock'
@@ -14,14 +37,21 @@ 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;
showHtmlComments?: boolean; // 是否显示 HTML 注释,默认为 false隐藏
editable?: boolean; // 是否可编辑,默认为 false
onContentChange?: (content: string) => void; // 内容变化回调
/** 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) => <h1 className="rb:text-2xl rb:font-bold rb:mb-2" {...props}>{children}</h1>,
h2: ({ children, ...props }: any) => <h2 className="rb:text-xl rb:font-bold rb:mb-2" {...props}>{children}</h2>,
@@ -38,7 +68,7 @@ const components = {
em: ({ children, ...props }: any) => <em className="rb:italic" {...props}>{children}</em>,
del: ({ children, ...props }: any) => <del className="rb:line-through" {...props}>{children}</del>,
span: ({ children, style, ...restProps }: any) => {
// 如果是 HTML 注释的 span应用特殊样式
// Apply special styling for HTML comment spans
if (style?.color === '#999') {
return <span style={{ color: '#999', fontSize: '0.9em' }}>{children}</span>
}
@@ -104,30 +134,33 @@ const RbMarkdown: FC<RbMarkdownProps> = ({
const [editContent, setEditContent] = useState(content)
const textareaRef = useRef<any>(null)
// 当外部 content 变化时,同步更新编辑内容
/** Sync edit content when external content changes */
useEffect(() => {
setEditContent(content)
}, [content])
// 处理 textarea 内容变化
/** Handle textarea content changes and trigger callback */
const handleTextareaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const newContent = e.target.value
setEditContent(newContent)
// 实时回调内容变化
/** Trigger real-time content change callback */
onContentChange?.(newContent)
}
// 根据参数决定是否将 HTML 注释转换为可见文本
// 使用特殊的 markdown 语法来显示注释,避免被 rehype-raw 过滤
/**
* 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(/<!--([\s\S]*?)-->/g, (_match, commentContent) => {
// 转换为带样式的文本,使用 <span class="html-comment"> 标记
/** Convert to styled text using span with html-comment class */
const escaped = commentContent.trim().replace(/</g, '&lt;').replace(/>/g, '&gt;')
return `<span class="html-comment">&lt;!-- ${escaped} --&gt;</span>`
})
: (editable ? editContent : content)
// 如果是编辑模式,显示 textarea
/** Render textarea in edit mode */
if (editable) {
return (
<div className="rb:relative">
@@ -138,21 +171,21 @@ const RbMarkdown: FC<RbMarkdownProps> = ({
}
`}</style>
{/* 编辑区域 */}
{/* Edit area with textarea */}
<Input.TextArea
ref={textareaRef}
value={editContent}
onChange={handleTextareaChange}
rows={10}
className="rb:font-mono rb:text-sm"
placeholder="请输入 Markdown 内容..."
placeholder="Enter Markdown content..."
style={{ resize: 'vertical' }}
/>
</div>
)
}
// 处理键盘快捷键
/** 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()
@@ -162,7 +195,7 @@ const RbMarkdown: FC<RbMarkdownProps> = ({
}
}
// 预览模式
/** Render markdown preview mode */
return (
<div className={`rb:relative ${className || ''}`} onKeyDown={handleKeyDown} tabIndex={0}>
<style>{`