diff --git a/static/douyin.html b/static/douyin.html index f57b4f7..563fc07 100644 --- a/static/douyin.html +++ b/static/douyin.html @@ -164,39 +164,53 @@ - - - - + + + + + diff --git a/static/js/douyin.js b/static/js/douyin.js index 6a10efc..1fa13e7 100644 --- a/static/js/douyin.js +++ b/static/js/douyin.js @@ -1,5 +1,75 @@ const { createApp, ref, computed, onMounted } = Vue; +// 1. 全局渲染器 - 纯手写的高鲁棒性解析器,不再依赖 marked +const globalRenderMarkdown = (text) => { + if (!text) return ''; + + // 0. 预处理:去除 AI 可能返回的代码块标记 + let cleanText = text.trim(); + // 去除开头的 ```markdown 或 ``` + cleanText = cleanText.replace(/^```(markdown)?\s*/i, ''); + // 去除结尾的 ``` + cleanText = cleanText.replace(/```\s*$/, ''); + + // 1. 预处理:按行分割 + const lines = cleanText.split('\n'); + let html = ''; + let inList = false; + + for (let i = 0; i < lines.length; i++) { + let line = lines[i]; + // 关键:去除行首尾空白,解决缩进导致的解析失败 + let trimmed = line.trim(); + + // 空行处理 + if (!trimmed) { + if (inList) { html += '\n'; inList = false; } + continue; // 忽略空行,或者可以加
+ } + + // 2. 内联格式处理(加粗、代码、链接) + // 加粗 **text** -> text + trimmed = trimmed.replace(/\*\*(.*?)\*\*/g, '$1'); + // 代码 `text` -> text + trimmed = trimmed.replace(/`([^`]+)`/g, '$1'); + + // 3. 块级元素处理 + // 标题 ### Title + if (trimmed.startsWith('###')) { + if (inList) { html += '\n'; inList = false; } + html += `

${trimmed.replace(/^###\s*/, '')}

`; + } + else if (trimmed.startsWith('##')) { + if (inList) { html += '\n'; inList = false; } + html += `

${trimmed.replace(/^##\s*/, '')}

`; + } + else if (trimmed.startsWith('#')) { + if (inList) { html += '\n'; inList = false; } + html += `

${trimmed.replace(/^#\s*/, '')}

`; + } + // 列表 - Item 或 * Item + else if (trimmed.startsWith('- ') || trimmed.startsWith('* ')) { + if (!inList) { html += '\n'; } + + return html; +}; + createApp({ setup() { const apiBase = ref(window.location.origin || "http://localhost:8000"); @@ -15,96 +85,26 @@ createApp({ const summaryLoading = ref(false); const summaryText = ref(''); - // 降级用的简易 Markdown 解析器 - const simpleMarkdown = (text) => { - if (!text) return ''; - let lines = text.split('\n'); - let html = ''; - let inList = false; - const parseInline = (str) => { - return str - .replace(/\*\*(.*?)\*\*/g, '$1') - .replace(/`(.*?)`/g, '$1'); - }; - for (let line of lines) { - let trimmed = line.trim(); - if (!trimmed) { - if (inList) { html += ''; inList = false; } - html += '
'; - continue; - } - if (trimmed.startsWith('### ')) { - if (inList) { html += ''; inList = false; } - html += `

${parseInline(trimmed.substring(4))}

`; - } else if (trimmed.startsWith('- ') || /^\d+\./.test(trimmed)) { - if (!inList) { html += ''; inList = false; } - html += `

${parseInline(trimmed)}

`; - } - } - if (inList) html += ''; - return html; - }; - - // Configure Marked - if (typeof marked !== 'undefined') { - marked.use({ - gfm: true, - breaks: true - }); - } - - // 增强的 Markdown & LaTeX 解析器 - const renderMarkdownAndLatex = (text) => { - if (!text) return ''; - - try { - let processedText = text; - - // 1. 处理 LaTeX - if (typeof katex !== 'undefined') { - // 处理块级 LaTeX: $$ ... $$ - processedText = processedText.replace(/\$\$\s*([\s\S]*?)\s*\$\$/g, (match, formula) => { - try { - return '
' + katex.renderToString(formula, { displayMode: true, throwOnError: false }) + '
'; - } catch (e) { return match; } - }); - - // 处理行内 LaTeX: $ ... $ - processedText = processedText.replace(/\$([^\$\n]+?)\$/g, (match, formula) => { - try { - return katex.renderToString(formula, { displayMode: false, throwOnError: false }); - } catch (e) { return match; } - }); - } - - // 2. 使用 marked 解析 Markdown - if (typeof marked !== 'undefined') { - // marked v12+ 使用 marked.parse - return marked.parse(processedText); - } else { - // 降级使用 simpleMarkdown - return simpleMarkdown(processedText); - } - } catch (e) { - console.error('Markdown rendering error:', e); - // 最后的兜底:如果 marked 报错,尝试用 simpleMarkdown - try { - return simpleMarkdown(text); - } catch (e2) { - return text; - } - } - }; - + // 使用全局渲染器 const renderedSummary = computed(() => { if (!summaryText.value) return ''; - return renderMarkdownAndLatex(summaryText.value); + const html = globalRenderMarkdown(summaryText.value); + return html; }); + // 这里的配置仍然保留,作为双重保险 + if (typeof marked !== 'undefined') { + const m = (typeof marked.marked === 'function') ? marked.marked : marked; + if (m.setOptions) { + m.setOptions({ + gfm: true, + breaks: true, + mangle: false, + headerIds: false + }); + } + } + // Methods const startParsing = async () => { if (!shareText.value.trim()) return;