Нет описания
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

markdown.pipe.ts 3.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import { Pipe, PipeTransform } from '@angular/core';
  2. import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
  3. import { marked } from 'marked';
  4. import Prism from 'prismjs';
  5. // 导入需要的语言支持
  6. import 'prismjs/components/prism-clike';
  7. import 'prismjs/components/prism-markup';
  8. import 'prismjs/components/prism-javascript';
  9. import 'prismjs/components/prism-typescript';
  10. import 'prismjs/components/prism-python';
  11. import 'prismjs/components/prism-go';
  12. import 'prismjs/components/prism-java';
  13. import 'prismjs/components/prism-csharp';
  14. import 'prismjs/components/prism-php';
  15. import 'prismjs/components/prism-ruby';
  16. import 'prismjs/components/prism-bash';
  17. import 'prismjs/components/prism-sql';
  18. import 'prismjs/components/prism-json';
  19. import 'prismjs/components/prism-yaml';
  20. import 'prismjs/components/prism-markdown';
  21. @Pipe({
  22. name: 'markdown',
  23. standalone: true
  24. })
  25. export class MarkdownPipe implements PipeTransform {
  26. constructor(private sanitizer: DomSanitizer) {}
  27. transform(value: string): SafeHtml {
  28. if (!value) {
  29. return '';
  30. }
  31. // 预处理:将标签替换为美化版本
  32. const processedContent = this.preprocessTags(value);
  33. // 配置marked(使用基本配置)
  34. marked.setOptions({
  35. gfm: true,
  36. breaks: true
  37. });
  38. // 解析markdown
  39. let html = marked.parse(processedContent) as string;
  40. // 后处理:添加代码高亮和样式
  41. html = this.postprocessCodeBlocks(html);
  42. // 安全注入HTML
  43. return this.sanitizer.bypassSecurityTrustHtml(html);
  44. }
  45. /**
  46. * 预处理:将文本标签替换为美化版本和可折叠区域
  47. */
  48. private preprocessTags(content: string): string {
  49. // 处理 [思考] 标签及其内容
  50. content = this.processTagWithContent(content, '思考', '💭', 'thinking');
  51. // 处理 [工具] 标签及其内容
  52. content = this.processTagWithContent(content, '工具', '🛠️', 'tool');
  53. // 处理 [错误] 标签
  54. content = content.replace(/\[错误\]/g, '<span class="error-tag">❌ 错误</span>');
  55. return content;
  56. }
  57. /**
  58. * 处理标签及其后的内容,转换为可折叠区域
  59. */
  60. private processTagWithContent(content: string, tagName: string, emoji: string, cssClass: string): string {
  61. // 正则表达式匹配 [标签] 及其后的内容(直到下一个标签或字符串结束)
  62. const pattern = new RegExp(`\\[${tagName}\\]\\s*([\\s\\S]*?)(?=\\n\\s*\\[|$)`, 'g');
  63. return content.replace(pattern, (match, tagContent) => {
  64. // 清理内容前后的空白
  65. const trimmedContent = tagContent.trim();
  66. if (!trimmedContent) {
  67. return `<details class="${cssClass}-details"><summary class="${cssClass}-summary">${emoji} ${tagName}</summary></details>`;
  68. }
  69. return `<details class="${cssClass}-details" ${cssClass === 'thinking' || cssClass === 'tool' ? '' : 'open'}><summary class="${cssClass}-summary">${emoji} ${tagName}</summary>${trimmedContent}</details>`;
  70. });
  71. }
  72. /**
  73. * 后处理:添加代码高亮和样式
  74. */
  75. private postprocessCodeBlocks(html: string): string {
  76. // 使用正则表达式匹配代码块
  77. const codeBlockRegex = /<pre><code\s*(?:class="language-([^"]+)")?>([\s\S]*?)<\/code><\/pre>/gi;
  78. html = html.replace(codeBlockRegex, (match, lang, code) => {
  79. const language = lang || 'plaintext';
  80. try {
  81. // 使用Prism高亮代码
  82. const highlighted = Prism.highlight(code, Prism.languages[language] || Prism.languages['plaintext'], language);
  83. return `<pre class="prism-code language-${language}"><code class="language-${language}">${highlighted}</code></pre>`;
  84. } catch (e) {
  85. // 如果高亮失败,返回原始代码块
  86. return `<pre class="prism-code language-${language}"><code class="language-${language}">${code}</code></pre>`;
  87. }
  88. });
  89. return html;
  90. }
  91. }