| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- // markdownRenderer.js
- export function renderMarkdown(raw) {
- // 过滤结束标记
- let text = raw.replace(/\[DONE\]/g, '')
- // ── 预处理:在块级标识前插入换行(处理 AI 不换行直接切换块的情况)──
- // 在 ## 标题前插入换行(前面不是换行时)
- text = text.replace(/([^\n])(#{1,6} )/g, '$1\n$2')
- // 在无序列表项前插入换行
- text = text.replace(/([^\n])([ \t]*[-*+] )/g, '$1\n$2')
- // 在有序列表项前插入换行
- text = text.replace(/([^\n])([ \t]*\d+\. )/g, '$1\n$2')
- // 在引用块前插入换行
- text = text.replace(/([^\n])(> )/g, '$1\n$2')
- // ── 按段落分割处理(双换行为段落分隔)──
- // 先把单换行统一,再按块处理
- const lines = text.split('\n')
- const blocks = []
- let i = 0
- while (i < lines.length) {
- const line = lines[i]
- // 空行跳过
- if (line.trim() === '') {
- i++
- continue
- }
- // 代码块
- if (line.startsWith('```')) {
- const lang = line.slice(3).trim()
- const codeLines = []
- i++
- while (i < lines.length && !lines[i].startsWith('```')) {
- codeLines.push(lines[i])
- i++
- }
- i++ // 跳过结束 ```
- const escaped = codeLines.join('\n')
- .replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
- blocks.push(`<pre><code${lang ? ` class="language-${lang}"` : ''}>${escaped}</code></pre>`)
- continue
- }
- // 标题
- const headingMatch = line.match(/^(#{1,6}) (.+)/)
- if (headingMatch) {
- const level = headingMatch[1].length
- blocks.push(`<h${level}>${applyInline(headingMatch[2])}</h${level}>`)
- i++
- continue
- }
- // 分割线
- if (/^[-*_]{3,}\s*$/.test(line)) {
- blocks.push('<hr>')
- i++
- continue
- }
- // 引用块
- if (line.startsWith('> ') || line === '>') {
- const content = line.slice(line.startsWith('> ') ? 2 : 1)
- blocks.push(`<blockquote>${applyInline(content)}</blockquote>`)
- i++
- continue
- }
- // 无序列表:收集连续的列表项
- if (/^[ \t]*[-*+] /.test(line)) {
- const items = []
- while (i < lines.length && /^[ \t]*[-*+] /.test(lines[i])) {
- const m = lines[i].match(/^[ \t]*[-*+] (.+)/)
- if (m) items.push(`<li>${applyInline(m[1])}</li>`)
- i++
- }
- blocks.push(`<ul>${items.join('')}</ul>`)
- continue
- }
- // 有序列表:收集连续的列表项
- if (/^[ \t]*\d+\. /.test(line)) {
- const items = []
- while (i < lines.length && /^[ \t]*\d+\. /.test(lines[i])) {
- const m = lines[i].match(/^[ \t]*\d+\. (.+)/)
- if (m) items.push(`<li>${applyInline(m[1])}</li>`)
- i++
- }
- blocks.push(`<ol>${items.join('')}</ol>`)
- continue
- }
- // 普通段落
- blocks.push(`<p>${applyInline(line)}</p>`)
- i++
- }
- return blocks.join('\n')
- }
- // 行内样式处理
- function applyInline(text) {
- return text
- .replace(/\*\*\*([^*\n]+?)\*\*\*/g, '<strong><em>$1</em></strong>')
- .replace(/\*\*([^*\n]+?)\*\*/g, '<strong>$1</strong>')
- .replace(/\*([^*\n]+?)\*/g, '<em>$1</em>')
- .replace(/~~([^~\n]+?)~~/g, '<del>$1</del>')
- .replace(/`([^`\n]+)`/g, '<code>$1</code>')
- .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>')
- }
|