markdown.js 2.9 KB

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