| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778 |
- /**
- * Markdown 富文本渲染工具。
- *
- * <p>统一为 chat / 工作流运行结果 / 运行记录页 三处提供一致的渲染:
- * 支持 GFM(表格 / 删除线 / 任务列表)、代码块(highlight.js 语法高亮)、
- * 行内与块级公式(KaTeX)。
- *
- * <p>使用:
- * ```js
- * import { renderMarkdown } from '@/utils/markdown'
- * // 在 Vue 模板中
- * <div v-html="renderMarkdown(text)" class="md-body" />
- * ```
- *
- * <p>样式:本工具只产出 HTML 字符串;视觉表现(字号、配色、表格边框)由调用方
- * 通过 {@code .md-body :deep(...)} CSS 控制,参考 ChatMessageList.vue 的 {@code .assistant-md}。
- * 语法高亮的 token 配色由 {@code styles/hljs-theme.css} 统一定义。
- */
- import { Marked } from 'marked'
- import { markedHighlight } from 'marked-highlight'
- import markedKatex from 'marked-katex-extension'
- import DOMPurify from 'dompurify'
- import hljs from 'highlight.js/lib/common'
- // KaTeX 样式:渲染数学公式必需,挂到全局一次即可
- import 'katex/dist/katex.min.css'
- // 语法高亮深色主题:仅 token 前景色,背景由调用方控制
- import '../styles/hljs-theme.css'
- // 模块级单例:避免每次调用都重新装配 marked
- // 语法高亮规则(使用 highlight.js/lib/common,含 30+ 常用语言,体积约 30KB):
- // 1) 显式指定语言(如 ```js)且已注册:用 hljs.highlight 精确高亮
- // 2) 未指定语言或语言未注册:用 hljs.highlightAuto 自动检测(限 50KB 以内避免性能问题)
- // 3) 异常或超长:降级为纯文本(hljs 主题的 .hljs 默认色)
- const marked = new Marked(
- markedHighlight({
- langPrefix: 'hljs language-',
- highlight(code, lang) {
- try {
- const language = lang && hljs.getLanguage(lang) ? lang : null
- if (language) {
- return hljs.highlight(code, { language }).value
- }
- if (code.length < 50000) {
- return hljs.highlightAuto(code).value
- }
- return code
- } catch (e) {
- return code
- }
- }
- })
- )
- marked.use({ gfm: true, breaks: true })
- marked.use(markedKatex({ throwOnError: false, output: 'html' }))
- /**
- * 把 markdown 字符串渲染为已 sanitize 的 HTML。
- *
- * <p>流程:marked.parse → DOMPurify.sanitize(防 XSS)→ HTML 字符串。
- * 异常时降级为对纯文本做 sanitize,保证不抛错。
- *
- * @param {string} text 原始 markdown 文本,可为空
- * @returns {string} 可安全 v-html 注入的 HTML
- */
- export function renderMarkdown(text) {
- if (!text) return ''
- try {
- const raw = marked.parse(text)
- // marked.parse 同步返回 string(未启用 async);保险起见 toString
- return DOMPurify.sanitize(String(raw), {
- ADD_TAGS: ['span', 'math', 'semantics', 'annotation'],
- ADD_ATTR: ['class']
- })
- } catch (e) {
- console.warn('[utils/markdown] 渲染失败', e?.message || e)
- return DOMPurify.sanitize(String(text))
- }
- }
|