parseMarkdown.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import MarkdownIt from 'markdown-it'
  2. import subscript from 'markdown-it-sub'
  3. import superscript from 'markdown-it-sup'
  4. import footnote from 'markdown-it-footnote'
  5. import taskLists from 'markdown-it-task-lists'
  6. import abbr from 'markdown-it-abbr'
  7. import container from 'markdown-it-container'
  8. import DOMPurify from 'dompurify'
  9. import highlightJs from 'highlight.js'
  10. import 'highlight.js/styles/github.css'
  11. highlightJs.configure({
  12. ignoreUnescapedHTML: true, // 防止XSS
  13. throwUnescapedHTML: true, // 安全检测
  14. languages: ['javascript', 'typescript', 'html', 'css', 'json', 'bash']
  15. })
  16. // 创建 markdown-it 实例
  17. const md = new MarkdownIt({
  18. html: true, // 启用html标签
  19. linkify: true, // 自动转换URL为链接
  20. typographer: true, // 启用排版美化
  21. highlight: (code, language) => {
  22. // 安全处理未转义内容
  23. const safeStr: any = md.utils.escapeHtml(code)
  24. if (language && highlightJs.getLanguage(language)) {
  25. try {
  26. return `<pre class="hljs"><code>${highlightJs.highlight(safeStr, { language: language, ignoreIllegals: true }).value
  27. }</code></pre>`
  28. } catch (__) {
  29. }
  30. }
  31. return `<pre class="hljs"><code>${safeStr}</code></pre>`
  32. }
  33. })
  34. md.use(subscript);
  35. md.use(superscript);
  36. md.use(footnote);
  37. md.use(taskLists);
  38. md.use(abbr);
  39. md.use(container);
  40. // 解析 Markdown 文本
  41. export const parseMarkdown = (markdownText: any) => {
  42. let rawHtml = md.render(markdownText)
  43. return DOMPurify.sanitize(rawHtml)
  44. }
  45. export const extractPlainText = (markdown: string) => {
  46. const md = new MarkdownIt({
  47. // 禁用所有HTML标签渲染
  48. html: false,
  49. // 禁用自动链接识别
  50. linkify: false,
  51. // 禁用自动换行转换
  52. breaks: false,
  53. // 禁用typographer转换(如引号转换)
  54. typographer: false
  55. });
  56. // 自定义渲染器,只返回文本内容
  57. const originalRender = md.renderer.rules.text;
  58. md.renderer.rules.text = function (tokens, idx, options, env, self) {
  59. return originalRender ? originalRender(tokens, idx, options, env, self) : tokens[idx].content;
  60. };
  61. let openFnc = function () {
  62. return '';
  63. };
  64. // 处理各种token类型
  65. md.renderer.rules.link_open = md.renderer.rules.link_close = openFnc;
  66. md.renderer.rules.strong_open = md.renderer.rules.strong_close = openFnc;
  67. md.renderer.rules.em_open = md.renderer.rules.em_close = openFnc;
  68. md.renderer.rules.heading_open = md.renderer.rules.heading_close = openFnc;
  69. md.renderer.rules.paragraph_open = md.renderer.rules.paragraph_close = openFnc;
  70. md.renderer.rules.list_item_open = md.renderer.rules.list_item_close = openFnc;
  71. md.renderer.rules.ordered_list_open = md.renderer.rules.ordered_list_close = openFnc;
  72. md.renderer.rules.bullet_list_open = md.renderer.rules.bullet_list_close = openFnc;
  73. md.renderer.rules.code_block = md.renderer.rules.fence = openFnc;
  74. md.renderer.rules.blockquote_open = md.renderer.rules.blockquote_close = openFnc;
  75. // 处理图片(完全忽略)
  76. md.renderer.rules.image = function () {
  77. return '';
  78. };
  79. // 渲染并清理结果
  80. return md.render(markdown)
  81. .replace(/\s+/g, ' ')
  82. .replace(/<[^>]*>/g, '')
  83. .trim();
  84. }