import { Marked } from 'marked'
import type { CSSProperties } from 'react'
import type { Tokens } from 'marked'
// 将 React CSSProperties 转换为 CSS 字符串
function cssPropertiesToString(style: React.CSSProperties = {}): string {
return Object.entries(style)
.map(([key, value]) => {
// 转换驼峰命名为连字符命名
const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase()
// 处理对象类型的值
if (value && typeof value === 'object') {
if ('toString' in value) {
return `${cssKey}: ${value.toString()}`
}
return `${cssKey}: ${JSON.stringify(value)}`
}
return `${cssKey}: ${value}`
})
.filter(Boolean)
.join(';')
}
// 将基础样式选项转换为 CSS 字符串
function baseStylesToString(base: RendererOptions['base'] = {}): string {
const styles: string[] = []
if (base.primaryColor) {
styles.push(`--md-primary-color: ${base.primaryColor}`)
}
if (base.textAlign) {
styles.push(`text-align: ${base.textAlign}`)
}
if (base.lineHeight) {
styles.push(`line-height: ${base.lineHeight}`)
}
return styles.join(';')
}
// 预处理函数
function preprocessMarkdown(markdown: string): string {
// 处理 ** 语法,但排除已经是 HTML 的部分
return markdown.replace(/(?]*)\*\*([^*]+)\*\*(?![^<]*>)/g, '$1')
}
// Initialize marked instance
const marked = new Marked()
// 创建基础渲染器
const baseRenderer = new marked.Renderer()
// 重写 strong 渲染器
baseRenderer.strong = function(text) {
return `${text}`
}
// 应用配置和渲染器
marked.setOptions({
gfm: true,
breaks: true,
async: false,
pedantic: false,
renderer: baseRenderer
})
export function convertToWechat(markdown: string, options: RendererOptions = {}): string {
// 创建渲染器
const customRenderer = new marked.Renderer()
// 继承基础渲染器
Object.setPrototypeOf(customRenderer, baseRenderer)
customRenderer.heading = function({ text, depth }: Tokens.Heading) {
const style = options.block?.[`h${depth}` as keyof typeof options.block]
const styleStr = cssPropertiesToString(style)
return `
${content}
` } customRenderer.blockquote = function({ text }: Tokens.Blockquote) { const style = options.block?.blockquote const styleStr = cssPropertiesToString(style) return `${text}` } customRenderer.code = function({ text, lang }: Tokens.Code) { const style = options.block?.code_pre const styleStr = cssPropertiesToString(style) return `
${text}
`
}
customRenderer.codespan = function({ text }: Tokens.Codespan) {
const style = options.inline?.codespan
const styleStr = cssPropertiesToString(style)
return `${text}
`
}
customRenderer.em = function({ text }: Tokens.Em) {
const style = options.inline?.em
const styleStr = cssPropertiesToString(style)
return `${text}`
}
customRenderer.strong = function({ text }: Tokens.Strong) {
const style = options.inline?.strong
const styleStr = cssPropertiesToString(style)
return `${text}`
}
customRenderer.link = function({ href, title, text }: Tokens.Link) {
const style = options.inline?.link
const styleStr = cssPropertiesToString(style)
return `${text}`
}
customRenderer.image = function({ href, title, text }: Tokens.Image) {
const style = options.block?.image
const styleStr = cssPropertiesToString(style)
return `${text}
` } // 使用自定义渲染器转换 Markdown return marked.parse(markdown, { renderer }) as string } type RendererOptions = { base?: { primaryColor?: string textAlign?: string lineHeight?: string | number } block?: { h1?: CSSProperties h2?: CSSProperties h3?: CSSProperties h4?: CSSProperties h5?: CSSProperties h6?: CSSProperties p?: CSSProperties blockquote?: CSSProperties code_pre?: CSSProperties image?: CSSProperties ul?: CSSProperties ol?: CSSProperties } inline?: { strong?: CSSProperties em?: CSSProperties codespan?: CSSProperties link?: CSSProperties listitem?: CSSProperties } } export type { RendererOptions }