wujingjing
2025-05-07 c26a0c5e20dfdcc45b43315b50296e90e30995d8
src/components/chat/libs/markdown.ts
@@ -1,21 +1,169 @@
import highlight from 'highlight.js';
// import highlight from 'highlight.js';
import hljs from 'highlight.js';
import Markdown from 'markdown-it';
// 导入 highlight.js 的样式,这里使用 github 主题
import 'highlight.js/styles/github.css';
const mdOptions: Markdown.Options = {
const md = new Markdown({
   linkify: true,
   typographer: true,
   breaks: true,
   langPrefix: 'language-',
   // 代码高亮
   highlight(str, lang) {
      if (lang && highlight.getLanguage(lang)) {
   highlight: (str: string, lang: string) => {
      if (lang && hljs.getLanguage(lang)) {
         try {
            return '<pre class="hljs"><code>' + highlight.highlight(lang, str, true).value + '</code></pre>';
         } catch (__) {
      }
            return hljs.highlight(str, { language: lang }).value;
         } catch {
            return str;
         }
      }
      return '';
      return str;
   },
});
// 自定义代码块渲染
md.renderer.rules.fence = (tokens, idx, options, env, slf) => {
   const token = tokens[idx];
   const lang = token.info || '';
   const highlighted = options.highlight?.(token.content, lang, '') || token.content;
   return `
      <div class="code-markdown-it-block">
         <div class="code-markdown-it-header">
            <div class="code-markdown-it-header-left">
               <span class="code-markdown-it-lang">${lang}</span>
            </div>
            <div class="code-markdown-it-header-right">
               <button title="复制" class="code-markdown-it-copy" onclick="this.classList.add('copied'); setTimeout(() => this.classList.remove('copied'), 1500); navigator.clipboard.writeText(this.parentElement.parentElement.nextElementSibling.textContent)">
                  <svg class="copy-icon" viewBox="0 0 16 16" width="16" height="16">
                     <path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path>
                     <path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path>
                  </svg>
                  <svg class="check-icon" viewBox="0 0 16 16" width="16" height="16">
                     <path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.75.75 0 0 1 1.06-1.06L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path>
                  </svg>
               </button>
               <button title="展开" class="code-markdown-it-toggle" onclick="const block = this.parentElement.parentElement.parentElement; block.classList.toggle('collapsed'); this.title = block.classList.contains('collapsed') ? '展开' : '折叠'">
                  <svg class="expand-icon" viewBox="0 0 24 24" width="16" height="16">
                     <path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"></path>
                  </svg>
               </button>
            </div>
         </div>
         <pre class="code-markdown-it-content"><code class="${options.langPrefix}${lang}">${highlighted}</code></pre>
      </div>
   `.trim();
};
export const md = new Markdown(mdOptions);
// 添加自定义样式
const style = document.createElement('style');
// 自定义段落渲染规则
md.renderer.rules.paragraph_open = (tokens, idx) => {
   return '<p class="inline-block">'; // 添加 inline-block 类来控制显示
};
// 自定义表格渲染规则
md.renderer.rules.table_open = (tokens, idx) => {
   return '<table class="markdown-table">';
};
style.textContent = `
   .code-markdown-it-block {
      margin: 1em 0;
      border-radius: 6px;
      overflow: hidden;
      border: 1px solid #e1e4e8;
   }
   .code-markdown-it-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 8px 16px;
      background: #f6f8fa;
      border-bottom: 1px solid #e1e4e8;
   }
   .code-markdown-it-header-left {
      display: flex;
      align-items: center;
   }
   .code-markdown-it-header-right {
      display: flex;
      align-items: center;
      gap: 8px;
   }
   .code-markdown-it-lang {
      font-size: 14px;
      font-weight: 600;
      color: #24292e;
      // text-transform: uppercase;
   }
   .code-markdown-it-copy {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      background: none;
      border: none;
      padding: 4px;
      cursor: pointer;
      color: #586069;
      border-radius: 4px;
   }
   .code-markdown-it-copy:hover {
      background: #f3f4f6;
      color: #24292e;
   }
   .code-markdown-it-copy .check-icon {
      display: none;
   }
   .code-markdown-it-copy.copied .copy-icon {
      display: none;
   }
   .code-markdown-it-copy.copied .check-icon {
      display: block;
      color: #22863a;
   }
   .code-markdown-it-toggle {
      background: none;
      border: none;
      padding: 4px;
      cursor: pointer;
      color: #586069;
      display: flex;
      align-items: center;
   }
   .code-markdown-it-toggle:hover {
      color: #24292e;
   }
   .code-markdown-it-content {
      margin: 0;
      padding: 16px;
      overflow-x: auto;
   }
   .code-markdown-it-block.collapsed .code-markdown-it-content {
      display: none;
   }
   .code-markdown-it-block.collapsed .expand-icon {
      transform: rotate(-90deg);
   }
   .expand-icon {
      transition: transform 0.2s;
      fill: currentColor;
   }
   .copy-icon, .check-icon {
      fill: currentColor;
   }
   .markdown-table {
      border-collapse: collapse;
      width: 100%;
   }
   .markdown-table thead {
      background-color: #f2f2f2;
   }
   .markdown-table th,
   .markdown-table td {
      border: 1px solid #ebebeb;
      padding: 8px;
      text-align: center;
   }
`;
document.head.appendChild(style);
export { md };