优化移动设备体验

This commit is contained in:
tianyaxiang 2025-01-30 00:25:19 +08:00
parent e7d5c59140
commit ae9ecdfa4b

View File

@ -1,11 +1,6 @@
import { marked, type Tokens } from 'marked' import { Marked } from 'marked'
import type { CSSProperties } from 'react' import type { CSSProperties } from 'react'
import type { Tokens } from 'marked'
// 配置 marked 选项
marked.setOptions({
gfm: true,
breaks: true
})
// 将 React CSSProperties 转换为 CSS 字符串 // 将 React CSSProperties 转换为 CSS 字符串
function cssPropertiesToString(style: React.CSSProperties = {}): string { function cssPropertiesToString(style: React.CSSProperties = {}): string {
@ -43,129 +38,144 @@ function baseStylesToString(base: RendererOptions['base'] = {}): string {
return styles.join(';') return styles.join(';')
} }
// 预处理函数
function preprocessMarkdown(markdown: string): string {
// 处理 ** 语法,但排除已经是 HTML 的部分
return markdown.replace(/(?<!<[^>]*)\*\*([^*]+)\*\*(?![^<]*>)/g, '<strong>$1</strong>')
}
// Initialize marked instance
const marked = new Marked()
// 创建基础渲染器
const baseRenderer = new marked.Renderer()
// 重写 strong 渲染器
baseRenderer.strong = function(text) {
return `<strong>${text}</strong>`
}
// 应用配置和渲染器
marked.setOptions({
gfm: true,
breaks: true,
async: false,
pedantic: false,
renderer: baseRenderer
})
export function convertToWechat(markdown: string, options: RendererOptions = {}): string { export function convertToWechat(markdown: string, options: RendererOptions = {}): string {
const renderer = new marked.Renderer() // 预处理 markdown
// 标题渲染 markdown = preprocessMarkdown(markdown)
renderer.heading = ({ text, depth }: Tokens.Heading) => {
// 创建渲染器
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 style = options.block?.[`h${depth}` as keyof typeof options.block]
const styleStr = cssPropertiesToString(style) const styleStr = cssPropertiesToString(style)
return `<h${depth}${styleStr ? ` style="${styleStr}"` : ''}>${text}</h${depth}>` return `<h${depth}${styleStr ? ` style="${styleStr}"` : ''}>${text}</h${depth}>`
} }
// 段落渲染 customRenderer.paragraph = function({ text }: Tokens.Paragraph) {
renderer.paragraph = (text) => {
const style = options.block?.p const style = options.block?.p
const styleStr = cssPropertiesToString(style) const styleStr = cssPropertiesToString(style)
const content = typeof text === 'object' ? (text.text || text.toString()) : text return `<p${styleStr ? ` style="${styleStr}"` : ''}>${text}</p>`
return `<p${styleStr ? ` style="${styleStr}"` : ''}>${content}</p>`
} }
// 引用渲染 customRenderer.blockquote = function({ text }: Tokens.Blockquote) {
renderer.blockquote = (quote) => {
const style = options.block?.blockquote const style = options.block?.blockquote
const styleStr = cssPropertiesToString(style) const styleStr = cssPropertiesToString(style)
const content = typeof quote === 'object' ? (quote.text || quote.toString()) : quote return `<blockquote${styleStr ? ` style="${styleStr}"` : ''}>${text}</blockquote>`
return `<blockquote${styleStr ? ` style="${styleStr}"` : ''}>${content}</blockquote>`
} }
// 代码块渲染 customRenderer.code = function({ text, lang }: Tokens.Code) {
renderer.code = ({ text, lang = '' }: { text: string; lang?: string }) => {
const style = options.block?.code_pre const style = options.block?.code_pre
const styleStr = cssPropertiesToString(style) const styleStr = cssPropertiesToString(style)
return `<pre${styleStr ? ` style="${styleStr}"` : ''}><code class="language-${lang}">${text}</code></pre>` return `<pre${styleStr ? ` style="${styleStr}"` : ''}><code class="language-${lang || ''}">${text}</code></pre>`
} }
// 行内代码渲染 customRenderer.codespan = function({ text }: Tokens.Codespan) {
renderer.codespan = (code) => {
const style = options.inline?.codespan const style = options.inline?.codespan
const styleStr = cssPropertiesToString(style) const styleStr = cssPropertiesToString(style)
return `<code${styleStr ? ` style="${styleStr}"` : ''}>${code}</code>` return `<code${styleStr ? ` style="${styleStr}"` : ''}>${text}</code>`
} }
// 强调(斜体)渲染 customRenderer.em = function({ text }: Tokens.Em) {
renderer.em = (text) => {
const style = options.inline?.em const style = options.inline?.em
const styleStr = cssPropertiesToString(style) const styleStr = cssPropertiesToString(style)
return `<em${styleStr ? ` style="${styleStr}"` : ''}>${text}</em>` return `<em${styleStr ? ` style="${styleStr}"` : ''}>${text}</em>`
} }
// 加粗渲染 customRenderer.strong = function({ text }: Tokens.Strong) {
renderer.strong = (text) => {
const style = options.inline?.strong const style = options.inline?.strong
const styleStr = cssPropertiesToString(style) const styleStr = cssPropertiesToString(style)
return `<strong${styleStr ? ` style="${styleStr}"` : ''}>${text}</strong>` return `<strong${styleStr ? ` style="${styleStr}"` : ''}>${text}</strong>`
} }
// 链接渲染 customRenderer.link = function({ href, title, text }: Tokens.Link) {
renderer.link = ({ href, title, tokens }: Tokens.Link) => {
const style = options.inline?.link const style = options.inline?.link
const styleStr = cssPropertiesToString(style) const styleStr = cssPropertiesToString(style)
const text = tokens?.map(t => 'text' in t ? t.text : '').join('') || ''
return `<a href="${href}"${title ? ` title="${title}"` : ''}${styleStr ? ` style="${styleStr}"` : ''}>${text}</a>` return `<a href="${href}"${title ? ` title="${title}"` : ''}${styleStr ? ` style="${styleStr}"` : ''}>${text}</a>`
} }
// 图片渲染 customRenderer.image = function({ href, title, text }: Tokens.Image) {
renderer.image = ({ href, title, text }: Tokens.Image) => {
const style = options.block?.image const style = options.block?.image
const styleStr = cssPropertiesToString(style) const styleStr = cssPropertiesToString(style)
return `<img src="${href}"${title ? ` title="${title}"` : ''} alt="${text}"${styleStr ? ` style="${styleStr}"` : ''} />` return `<img src="${href}"${title ? ` title="${title}"` : ''} alt="${text}"${styleStr ? ` style="${styleStr}"` : ''} />`
} }
// 列表渲染 customRenderer.list = function(body: Tokens.List) {
renderer.list = (token: Tokens.List) => { const ordered = body.ordered
const tag = token.ordered ? 'ol' : 'ul' const tag = ordered ? 'ol' : 'ul'
const style = options.block?.[token.ordered ? 'ol' : 'ul'] const style = options.block?.[ordered ? 'ol' : 'ul']
const styleStr = cssPropertiesToString(style) const styleStr = cssPropertiesToString(style)
return `<${tag}${styleStr ? ` style="${styleStr}"` : ''}>${token.items.map(item => item.text).join('')}</${tag}>` return `<${tag}${styleStr ? ` style="${styleStr}"` : ''}>${body.raw}</${tag}>`
} }
// 列表项渲染 customRenderer.listitem = function(item: Tokens.ListItem) {
renderer.listitem = (text) => {
const style = options.inline?.listitem const style = options.inline?.listitem
const styleStr = cssPropertiesToString(style) const styleStr = cssPropertiesToString(style)
return `<li${styleStr ? ` style="${styleStr}"` : ''}>${text}</li>` return `<li${styleStr ? ` style="${styleStr}"` : ''}>${item.text}</li>`
} }
marked.use({ renderer }) // Convert Markdown to HTML using the custom renderer
const html = marked.parse(markdown, { renderer: customRenderer }) as string
// 转换 Markdown 为 HTML // Apply base styles
const html = marked.parse(markdown, { async: false }) as string
// 应用基础样式
const baseStyles = baseStylesToString(options.base) const baseStyles = baseStylesToString(options.base)
return baseStyles ? `<div style="${baseStyles}">${html}</div>` : html return baseStyles ? `<div style="${baseStyles}">${html}</div>` : html
} }
// 转换为小红书格式
export function convertToXiaohongshu(markdown: string): string { export function convertToXiaohongshu(markdown: string): string {
// 配置小红书特定的样式 // 预处理 markdown
const xiaohongshuRenderer = new marked.Renderer() markdown = preprocessMarkdown(markdown)
xiaohongshuRenderer.heading = ({ text, depth }: Tokens.Heading) => { const renderer = new marked.Renderer()
// 自定义渲染规则
renderer.heading = function({ text, depth }: Tokens.Heading) {
const fontSize = { const fontSize = {
1: '20px', [1]: '20px',
2: '18px', [2]: '18px',
3: '16px', [3]: '16px',
4: '15px', [4]: '15px',
5: '14px', [5]: '14px',
6: '14px' [6]: '14px'
}[depth] }[depth] || '14px'
return `<h${depth} style="margin-top: 25px; margin-bottom: 12px; font-weight: bold; font-size: ${fontSize}; color: #222;">${text}</h${depth}>` return `<h${depth} style="margin-top: 25px; margin-bottom: 12px; font-weight: bold; font-size: ${fontSize}; color: #222;">${text}</h${depth}>`
} }
xiaohongshuRenderer.paragraph = (text) => { renderer.paragraph = function({ text }: Tokens.Paragraph) {
return `<p style="margin-bottom: 16px; line-height: 1.6; font-size: 15px; color: #222;">${text}</p>` return `<p style="margin-bottom: 16px; line-height: 1.6; font-size: 15px; color: #222;">${text}</p>`
} }
marked.setOptions({ renderer: xiaohongshuRenderer }) // 使用自定义渲染器转换 Markdown
return marked.parse(markdown, { renderer }) as string
let html = marked(markdown)
html = `<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, sans-serif; color: #222; line-height: 1.6;">${html}</div>`
return html
} }
type RendererOptions = { type RendererOptions = {