Files
Scoring-System/miniprogram/src/components/MarkdownReader/index.tsx
爽哒哒 f26d35da66
All checks were successful
Deploy to Server / deploy (push) Successful in 18s
创赢未来评分系统 - 初始化提交(移除大文件)
2026-03-18 22:41:23 +08:00

147 lines
5.7 KiB
TypeScript

import React, { useMemo } from 'react'
import { View, RichText, Video } from '@tarojs/components'
import { marked, Renderer } from 'marked'
import CodeBlock from './CodeBlock'
import './index.scss'
interface Props {
content: string
themeColor?: string
}
const MarkdownReader: React.FC<Props> = ({ content, themeColor = '#00b96b' }) => {
const elements = useMemo(() => {
if (!content) return []
const tokens = marked.lexer(content)
const result: React.ReactNode[] = []
let currentTokens: any[] = []
// Configure renderer
const renderer = new Renderer()
renderer.table = (header, body) => {
return `<div style="overflow-x: auto; width: 100%; -webkit-overflow-scrolling: touch;">
<table style="width: 100%; min-width: 600px; border-collapse: collapse; margin: 16px 0; font-size: 16px;">
<thead>${header}</thead>
<tbody>${body}</tbody>
</table>
</div>`
}
renderer.tablecell = (content, flags) => {
const type = flags.header ? 'th' : 'td'
const style = [
'border: 1px solid rgba(255,255,255,0.1)',
'padding: 10px',
flags.header ? 'background-color: rgba(255,255,255,0.05); font-weight: 700; color: #fff;' : 'color: #ddd;',
flags.align ? `text-align: ${flags.align}` : 'text-align: left'
].join(';')
return `<${type} style="${style}">${content}</${type}>`
}
renderer.image = (href, title, text) => {
return `<img src="${href}" style="max-width:100%;border-radius:8px;margin:10px 0;box-shadow: 0 4px 12px rgba(0,0,0,0.3);" title="${title || ''}" alt="${text || ''}" />`
}
renderer.link = (href, title, text) => {
return `<a href="${href}" style="color: ${themeColor}; text-decoration: none;">${text}</a>`
}
// Process tokens
tokens.forEach((token, index) => {
if (token.type === 'code') {
// Skip css blocks that look like the video component styles
if ((token.lang === 'css' || !token.lang) && token.text.includes('.simple-tech-video')) {
return
}
// Flush accumulated tokens
if (currentTokens.length > 0) {
// preserve links if any
(currentTokens as any).links = (tokens as any).links
const html = marked.parser(currentTokens as any, { renderer, breaks: true })
result.push(<RichText key={`rt-${index}`} nodes={html} className='markdown-text' />)
currentTokens = []
}
// Add code block
result.push(
<View key={`cb-${index}`} className='code-block-wrapper'>
<CodeBlock
code={token.text}
language={token.lang}
/>
</View>
)
} else if (token.type === 'html') {
// Check for video tag
const videoRegex = /<video[^>]*>[\s\S]*?<source[^>]*src=["'](.*?)["'][^>]*>[\s\S]*?<\/video>/i
const simpleVideoRegex = /<video[^>]*src=["'](.*?)["'][^>]*>/i
const match = token.text.match(videoRegex) || token.text.match(simpleVideoRegex)
if (match) {
// Flush accumulated tokens
if (currentTokens.length > 0) {
(currentTokens as any).links = (tokens as any).links
const html = marked.parser(currentTokens as any, { renderer, breaks: true })
result.push(<RichText key={`rt-${index}`} nodes={html} className='markdown-text' />)
currentTokens = []
}
const src = match[1]
// Try to extract caption
const captionRegex = /class=["']video-caption["'][^>]*>(.*?)<\/div>/i
const captionMatch = token.text.match(captionRegex)
const caption = captionMatch ? captionMatch[1] : null
result.push(
<View key={`video-${index}`} className='markdown-video-container'>
<Video
src={src}
className='markdown-video'
controls
autoplay={false}
objectFit='contain'
showFullscreenBtn
showPlayBtn
showCenterPlayBtn
enablePlayGesture
/>
{caption && <View className='markdown-video-caption'>{caption}</View>}
</View>
)
} else {
// Filter out style tags for video component if they are parsed as HTML
if (token.text.includes('<style>') && token.text.includes('.simple-tech-video')) {
// If it's JUST the style tag, ignore it. If it's mixed with other content, we might need to be careful.
// But usually marked parses block HTML separately.
// Let's verify if we can just skip it.
// If the token is ONLY the style block, we skip it.
// If it contains other content, we might need to strip the style.
// For now, let's assume it's a block HTML token.
return
}
currentTokens.push(token)
}
} else {
currentTokens.push(token)
}
})
// Flush remaining tokens
if (currentTokens.length > 0) {
(currentTokens as any).links = (tokens as any).links
const html = marked.parser(currentTokens as any, { renderer, breaks: true })
result.push(<RichText key={`rt-end`} nodes={html} className='markdown-text' />)
}
return result
}, [content, themeColor])
return <View className='markdown-reader'>{elements}</View>
}
export default MarkdownReader