├── .gitignore ├── README.md ├── docs ├── assets │ ├── index-D7ADuXm3.css │ └── index-Dkuq6TsQ.js └── index.html ├── eslint.config.js ├── example ├── basic │ ├── data.json │ └── index.tsx ├── cdm │ ├── cozeData.ts │ ├── define.ts │ ├── index.tsx │ └── util.ts ├── index.css └── index.tsx ├── index.html ├── license ├── package-lock.json ├── package.json ├── prettier.config.js ├── scripts ├── prepublish.js └── set-tag.cjs ├── shim.d.ts ├── src ├── AutoScroll │ └── index.tsx ├── Markdown │ └── index.tsx ├── MarkdownCMD │ └── index.tsx ├── components │ ├── BlockWrap │ │ ├── index.less │ │ └── index.tsx │ └── HighReactMarkdown │ │ ├── highlightTheme.less │ │ ├── index.less │ │ └── index.tsx ├── constant.ts ├── defined.less ├── defined.ts ├── index.less ├── index.tsx └── style.less ├── tsconfig-base.json ├── tsconfig-cjs.json ├── tsconfig-esm.json ├── tsconfig.json ├── vite-env.d.ts ├── vite.build.config.ts └── vite.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ds-markdown 2 | 3 | `ds-markdown`是一个[React](https://react.dev)组件, 类似[deepseek官网](https://chat.deepseek.com/)风格的 `Markdown` 4 | 5 | [DEMO](https://onshinpei.github.io/ds-markdown/) 6 | 7 | ## 特性 8 | 9 | - 🦮 对`deepseek`官网的聊天响应效果进行了1:1还原 10 | - 🛠 自带打字效果 11 | - 🦮 内部封装了常用的`markdown`格式的文本显示 12 | - 🔤 对大文档进行了性能优化,进行分批处理,生成打字效果的时候不会对页面造成卡顿现象 13 | 14 | ## 安装 15 | 16 | ```bash 17 | npm install ds-markdown 18 | ``` 19 | 20 | npm version 21 | npm downloads Min gzipped size 22 | 23 | ## props 24 | 25 | ### 默认导出 26 | 27 | | 属性名 | 类型 | 说明 | 默认值 | 28 | | ------------- | -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | -------- | 29 | | `interval` | `number` | 打字的速度`ms` | `30` | 30 | | `answerType` | `thinking` \| `answer` | `markdown`类型 | `answer` | 31 | | `onEnd` | `(data: { str: string; answerType: AnswerType }) => void` | 打字结束后回调,**可能会触发多次,因为AI的响应可能是一段一段,间隔可能较久** | - | 32 | | `onStart` | `(data: { currentIndex: number; currentChar: string; answerType: AnswerType; prevStr: string }) => void` | 打字开始回调 **可能会触发多次** | - | 33 | | `onTypedChar` | `(data: { currentIndex: number; currentChar: string; answerType: AnswerType; prevStr: string }) => void` | 当前正在打字的回调 | - | 34 | 35 | ## 使用示例 - default export 36 | 37 | [在线查看](https://stackblitz.com/edit/vitejs-vite-ddfw8avb?file=src%2FApp.tsx) 38 | 39 | ```tsx 40 | import { useState } from 'react'; 41 | import DsMarkdown from 'ds-markdown'; 42 | import 'ds-markdown/style.css'; 43 | 44 | const markdown = `# ds-markdown 45 | 46 | \`ds-markdown\`是一个[React](https://react.dev)组件, 类似[deepseek官网](https://chat.deepseek.com/)风格的 \`Markdown\` 47 | 48 | ## 特性 49 | 50 | - 🦮 对`deepseek`官网的聊天响应效果进行了1:1还原 51 | - 🛠 自带打字效果 52 | - 🦮 内部封装了常用的\`markdown\`格式的文本显示 53 | - 🔤 对大文档进行了性能优化,进行分批处理,生成打字效果的时候不会对页面造成卡顿现象 54 | `; 55 | 56 | function App() { 57 | const [thinkingContent, setThinkingContent] = useState(''); 58 | const [answerContent, setAnswerContent] = useState(''); 59 | 60 | const onClick = () => { 61 | // 如果重复点击,则会清空之前的效果 62 | setThinkingContent('这是我思考的内容,我已经思考完成,下面是我的答案'); 63 | }; 64 | 65 | console.log(answerContent); 66 | return ( 67 |
68 | 69 | { 73 | console.log('思考完成'); 74 | setAnswerContent(markdown); 75 | }} 76 | > 77 | {thinkingContent} 78 | 79 | 80 | {!!answerContent && ( 81 | 82 | {answerContent} 83 | 84 | )} 85 |
86 | ); 87 | } 88 | 89 | export default App; 90 | ``` 91 | 92 | ## 命令式示例 93 | 94 | 上面的示例中使用声明式方式来进行`markdown`的打字效果,当我们用流式拉取到数据时,文字是一个不断变化的过程,我们可以进行命令式的方式来加入文字,这样可以减少`markdown`的`rerender` 95 | 使用方式: 96 | `import { MarkdownCMD } from 'ds-markdown';` 97 | 98 | [在线查看](https://stackblitz.com/edit/vitejs-vite-2ri8kex3?file=src%2FApp.tsx) 99 | 100 | ```tsx 101 | import { useRef, useState } from 'react'; 102 | import { MarkdownCMD } from 'ds-markdown'; 103 | import 'ds-markdown/style.css'; 104 | 105 | const markdown = `# ds-markdown 106 | 107 | \`ds-markdown\`是一个[React](https://react.dev)组件, 类似[deepseek官网](https://chat.deepseek.com/)风格的 \`Markdown\` 108 | 109 | ## 特性 110 | 111 | - 🦮 对`deepseek`官网的聊天响应效果进行了1:1还原 112 | - 🛠 自带打字效果 113 | - 🦮 内部封装了常用的\`markdown\`格式的文本显示 114 | - 🔤 对大文档进行了性能优化,进行分批处理,生成打字效果的时候不会对页面造成卡顿现象 115 | `; 116 | 117 | function App() { 118 | const ref = useRef(); 119 | 120 | const onClick = () => { 121 | // 如果重复点击,则会清空之前的效果 122 | ref.current.clear(); 123 | // 显示思考过程 124 | ref.current.push('这是思考过程:我正在思考 ds-markdown是什么\n\n思考完成,准备发送答案', 'thinking'); 125 | // 显示结果 126 | ref.current.push(markdown, 'answer'); 127 | }; 128 | 129 | return ( 130 |
131 | 132 | 133 |
134 | ); 135 | } 136 | 137 | export default App; 138 | ``` 139 | 140 | ## 兼容性 141 | 142 | 由于本组件采用`react hooks`编写,所以`react`版本最低为`v16.8.0` 143 | 144 | ## 期待中的功能 145 | 146 | - 支持多种主题(开发中) 147 | - 增加测试用例(开发中) 148 | - 支持H5端(未开始,预计2025年5月底完成) 149 | - 支持微信、支付宝小程序端(未开始,预计2025年6月底完成) 150 | 151 | 由于本页面仔写业务写的飞起,只能在闲暇之余来开发本库,也希望各位小伙伴一起来共建😘 152 | 153 | ## License 154 | 155 | MIT 156 | -------------------------------------------------------------------------------- /docs/assets/index-D7ADuXm3.css: -------------------------------------------------------------------------------- 1 | :root{--ds-rgb-neutral-50: 250 250 250;--ds-rgb-neutral-100: 245 245 245;--ds-rgb-neutral-150: 237 237 237;--ds-rgb-neutral-200: 229 229 229;--ds-rgb-neutral-250: 220 220 220;--ds-rgb-neutral-300: 212 212 212;--ds-rgb-neutral-350: 187 187 187;--ds-rgb-neutral-400: 163 163 163;--ds-rgb-neutral-450: 139 139 139;--ds-rgb-neutral-500: 115 115 115;--ds-rgb-neutral-550: 98 98 98;--ds-rgb-neutral-600: 82 82 82;--ds-rgb-neutral-650: 73 73 73;--ds-rgb-neutral-700: 64 64 64;--ds-rgb-neutral-750: 51 51 51;--ds-rgb-neutral-800: 38 38 38;--ds-rgb-neutral-850: 30 30 30;--ds-rgb-neutral-900: 23 23 23;--ds-rgb-neutral-950: 10 10 10;--ds-rgb-blue-500: 59 130 246}.ds-markdown{--ds-font-family-code: Menlo, "Roboto Mono", "Courier New", Courier, monospace, "Inter", sans-serif}.ds-markdown{--ds-rgb-primary: var(--ds-rgb-blue-500);--ds-rgb-label-1: var(--ds-rgb-neutral-700);--ds-rgb-label-2: var(--ds-rgb-neutral-450);--ds-rgb-label-3: var(--ds-rgb-neutral-400);--dsr-border-1: rgb(var(--ds-rgb-neutral-350));--dsr-border-2: rgb(var(--ds-rgb-neutral-200))}.ds-markdown{--ds-font-size-m: 14px;--ds-font-size-xsp: 11px;--ds-line-height-m: 25px;--ds-transition-duration: .2s;--ds-ease-in-out: cubic-bezier(.4, 0, .2, 1);--ds-rgba-transparent: 255 255 255 / 0;--ds-rgb-link: var(--ds-rgb-primary)}.ds-markdown.apple{--ds-font-weight-strong: 500}.ds-markdown{--ds-md-zoom: 1.143;--ds-md-font-size: calc(var(--ds-md-zoom) * var(--ds-font-size-m));--ds-md-line-height: calc(var(--ds-md-zoom) * var(--ds-line-height-m));font-size:var(--ds-md-font-size);min-height:var(--ds-md-font-size);line-height:var(--ds-md-line-height)}.ds-markdown img{max-width:100%}.ds-markdown h1,.ds-markdown h2,.ds-markdown h3,.ds-markdown h4,.ds-markdown h5,.ds-markdown h6{font-weight:var(--ds-font-weight-strong);font-size:var(--ds-md-font-size);line-height:var(--ds-md-line-height);margin:calc(var(--ds-md-zoom) * 16px) 0 calc(var(--ds-md-zoom) * 12px) 0}.ds-markdown h1{font-size:calc(var(--ds-md-zoom) * 24px);line-height:1.5}.ds-markdown h2{font-size:calc(var(--ds-md-zoom) * 20px);line-height:1.5}.ds-markdown h3{font-size:calc(var(--ds-md-zoom) * 16px);line-height:1.5}.ds-markdown h1 .header-anchor,.ds-markdown h2 .header-anchor,.ds-markdown h3 .header-anchor,.ds-markdown h4 .header-anchor,.ds-markdown h5 .header-anchor,.ds-markdown h6 .header-anchor{opacity:0;margin-left:4px}.ds-markdown h1:hover .header-anchor,.ds-markdown h2:hover .header-anchor,.ds-markdown h3:hover .header-anchor,.ds-markdown h4:hover .header-anchor,.ds-markdown h5:hover .header-anchor,.ds-markdown h6:hover .header-anchor{opacity:1}.ds-markdown .ds-markdown-paragraph{margin:calc(var(--ds-md-zoom) * 12px) 0;font-size:var(--ds-md-font-size);line-height:var(--ds-md-line-height)}.ds-markdown a:not(.ds-a){color:rgb(var(--ds-rgb-link));transition:box-shadow var(--ds-transition-duration) var(--ds-ease-in-out);border-radius:calc(var(--ds-md-zoom) * 6px);border-left:3px solid rgba(var(--ds-rgba-transparent));border-right:3px solid rgba(var(--ds-rgba-transparent));border-top:2px solid rgba(var(--ds-rgba-transparent));border-bottom:2px solid rgba(var(--ds-rgba-transparent));margin-left:-3px;margin-right:-3px;text-decoration:none;position:relative}.ds-markdown a:not(.ds-a):focus{outline:none}.ds-markdown a:not(.ds-a):focus-visible{box-shadow:0 0 0 2px rgb(var(--ds-rgb-primary))}.ds-markdown li>ul,.ds-markdown li>ol{margin-top:4px}.ds-markdown ul,.ds-markdown ol{margin:calc(var(--ds-md-zoom) * 12px) 0;padding-left:calc(var(--ds-md-zoom) * 24px)}.ds-markdown li:not(:first-child){margin-top:4px}.ds-markdown li::marker{line-height:var(--ds-md-line-height);color:rgb(var(--ds-rgb-label-2))}.ds-markdown hr{height:1px;margin:32px 0;background-color:var(--dsr-border-2);border:none;display:block}.ds-markdown blockquote{border-left:2px solid rgb(var(--ds-rgb-label-3));padding-left:calc(var(--ds-md-zoom) * 16px);margin:0}.ds-markdown table{border-collapse:collapse}.ds-markdown th{color:rgb(var(--ds-rgb-label-1));padding:calc(var(--ds-md-zoom) * 6px) calc(var(--ds-md-zoom) * 12px);border-bottom:1px solid rgb(var(--ds-rgb-label-3));border-top:1px solid rgb(var(--ds-rgb-label-3));font-weight:600}.ds-markdown th,.ds-markdown td{text-align:left}.ds-markdown th:first-child{padding-left:0}.ds-markdown td{padding:calc(var(--ds-md-zoom) * 6px) calc(var(--ds-md-zoom) * 12px);border-bottom:1px solid rgb(var(--ds-rgb-label-3))}.ds-markdown td:first-child{padding-left:0}.ds-markdown pre{margin:calc(var(--ds-md-zoom) * 12px) 0;font-family:var(--ds-font-family-code);overflow:auto}.ds-markdown code{font-size:.875em;font-weight:var(--ds-font-weight-strong);font-family:var(--ds-font-family-code);background-color:#ececec;border-radius:4px;padding:.15rem .3rem}.ds-markdown code:before{margin-left:4px}.ds-markdown code:after{margin-right:4px}ds-markdown-math{text-align:center;display:block;overflow-x:auto;overflow-y:hidden}.ds-markdown>* .ds-markdown-math{margin:calc(var(--ds-md-zoom) * 12px) 0}.ds-markdown>* .ds-markdown-math:first-child{margin-top:0}.ds-markdown>* .ds-markdown-math:last-child{margin-bottom:0}.ds-markdown-code-copy-button{background-color:rgba(var(--ds-rgba-transparent));color:inherit;cursor:pointer;border:none;margin:0;padding:0}.ds-markdown li>.ds-markdown-paragraph{margin:4px 0}.ds-markdown li>:first-child{margin-top:0}.ds-markdown li>:last-child{margin-bottom:0}.ds-markdown .ds-markdown-paragraph:last-child{margin-bottom:0!important}.ds-markdown>:first-child{margin-top:0!important}.ds-markdown .ds-typed-thinking{border-left:2px solid #e5e5e5;padding:0 0 0 12px;color:#8b8b8b;line-height:26px;font-size:var(--ds-font-size-m)}.ds-markdown.ds-typed-answer{margin:calc(var(--ds-md-zoom) * 12px) 0;font-size:var(--ds-md-font-size);line-height:var(--ds-md-line-height)}.md-code-block-banner-content{justify-content:space-between;align-items:center;padding:6px 14px 6px 6px;display:flex}.md-code-block-language{color:var(--dsr-text-2);margin-left:8px;font-size:12px;line-height:18px}.ds-markdown .markdown-table-wrapper{overflow-x:auto}.ds-markdown .markdown-table-wrapper table{width:max-content;max-width:max-content;min-width:100%}.ds-markdown .markdown-table-wrapper th,.ds-markdown .markdown-table-wrapper td{padding:10px;font-size:15px;line-height:1.72}.ds-markdown .markdown-table-wrapper th{border-color:var(--dsr-border-1);border-top:none}.ds-markdown .markdown-table-wrapper td{border-color:var(--dsr-border-2);max-width:320px;min-width:100px;max-width:max(30vw,320px)}.md-code-block.md-code-block-dark code[class*=language-],.md-code-block.md-code-block-dark pre[class*=language-]{color:#e3eaf2;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;-moz-tab-size:4;tab-size:4;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none;background:0 0;font-family:Consolas,Monaco,Andale Mono,Ubuntu Mono,monospace;line-height:1.5}.md-code-block.md-code-block-dark pre[class*=language-]::-moz-selection{background:#3c526d}.md-code-block.md-code-block-dark pre[class*=language-]::selection{background:#3c526d}.md-code-block.md-code-block-dark pre[class*=language-] ::-moz-selection{background:#3c526d}.md-code-block.md-code-block-dark pre[class*=language-] ::selection{background:#3c526d}.md-code-block.md-code-block-dark code[class*=language-]::-moz-selection{background:#3c526d}.md-code-block.md-code-block-dark code[class*=language-]::selection{background:#3c526d}.md-code-block.md-code-block-dark code[class*=language-] ::-moz-selection{background:#3c526d}.md-code-block.md-code-block-dark code[class*=language-] ::selection{background:#3c526d}.md-code-block.md-code-block-dark pre[class*=language-]{margin:.5em 0;padding:1em;overflow:auto}.md-code-block.md-code-block-dark :not(pre)>code[class*=language-],.md-code-block.md-code-block-dark pre[class*=language-]{background:#111b27}.md-code-block.md-code-block-dark :not(pre)>code[class*=language-]{white-space:normal;border-radius:.3em;padding:.1em .3em}.md-code-block.md-code-block-dark .token.comment,.md-code-block.md-code-block-dark .token.prolog,.md-code-block.md-code-block-dark .token.doctype,.md-code-block.md-code-block-dark .token.cdata{color:#8da1b9}.md-code-block.md-code-block-dark .token.punctuation{color:#e3eaf2}.md-code-block.md-code-block-dark .token.delimiter.important,.md-code-block.md-code-block-dark .token.selector .parent,.md-code-block.md-code-block-dark .token.tag,.md-code-block.md-code-block-dark .token.tag .token.punctuation{color:#6cc}.md-code-block.md-code-block-dark .token.attr-name,.md-code-block.md-code-block-dark .token.boolean,.md-code-block.md-code-block-dark .token.boolean.important,.md-code-block.md-code-block-dark .token.number,.md-code-block.md-code-block-dark .token.constant,.md-code-block.md-code-block-dark .token.selector .token.attribute{color:#e6d37a}.md-code-block.md-code-block-dark .token.class-name,.md-code-block.md-code-block-dark .token.key,.md-code-block.md-code-block-dark .token.parameter,.md-code-block.md-code-block-dark .token.property,.md-code-block.md-code-block-dark .token.property-access,.md-code-block.md-code-block-dark .token.variable{color:#6cb8e6}.md-code-block.md-code-block-dark .token.attr-value,.md-code-block.md-code-block-dark .token.inserted,.md-code-block.md-code-block-dark .token.color,.md-code-block.md-code-block-dark .token.selector .token.value,.md-code-block.md-code-block-dark .token.string,.md-code-block.md-code-block-dark .token.string .token.url-link{color:#91d076}.md-code-block.md-code-block-dark .token.builtin,.md-code-block.md-code-block-dark .token.keyword-array,.md-code-block.md-code-block-dark .token.package,.md-code-block.md-code-block-dark .token.regex{color:#f4adf4}.md-code-block.md-code-block-dark .token.function,.md-code-block.md-code-block-dark .token.selector .token.class,.md-code-block.md-code-block-dark .token.selector .token.id{color:#c699e3}.md-code-block.md-code-block-dark .token.atrule .token.rule,.md-code-block.md-code-block-dark .token.combinator,.md-code-block.md-code-block-dark .token.keyword,.md-code-block.md-code-block-dark .token.operator,.md-code-block.md-code-block-dark .token.pseudo-class,.md-code-block.md-code-block-dark .token.pseudo-element,.md-code-block.md-code-block-dark .token.selector,.md-code-block.md-code-block-dark .token.unit{color:#e9ae7e}.md-code-block.md-code-block-dark .token.deleted,.md-code-block.md-code-block-dark .token.important{color:#cd6660}.md-code-block.md-code-block-dark .token.keyword-this,.md-code-block.md-code-block-dark .token.this{color:#6cb8e6}.md-code-block.md-code-block-dark .token.important,.md-code-block.md-code-block-dark .token.keyword-this,.md-code-block.md-code-block-dark .token.this,.md-code-block.md-code-block-dark .token.bold{font-weight:700}.md-code-block.md-code-block-dark .token.delimiter.important{font-weight:inherit}.md-code-block.md-code-block-dark .token.italic{font-style:italic}.md-code-block.md-code-block-dark .token.entity{cursor:help}.md-code-block.md-code-block-dark .language-markdown .token.title,.md-code-block.md-code-block-dark .language-markdown .token.title .token.punctuation{color:#6cb8e6;font-weight:700}.md-code-block.md-code-block-dark .language-markdown .token.blockquote.punctuation{color:#f4adf4}.md-code-block.md-code-block-dark .language-markdown .token.code{color:#6cc}.md-code-block.md-code-block-dark .language-markdown .token.hr.punctuation{color:#6cb8e6}.md-code-block.md-code-block-dark .language-markdown .token.url .token.content{color:#91d076}.md-code-block.md-code-block-dark .language-markdown .token.url-link{color:#e6d37a}.md-code-block.md-code-block-dark .language-markdown .token.list.punctuation{color:#f4adf4}.md-code-block.md-code-block-dark .language-markdown .token.table-header,.md-code-block.md-code-block-dark .language-json .token.operator{color:#e3eaf2}.md-code-block.md-code-block-dark .language-scss .token.variable{color:#6cc}.md-code-block.md-code-block-dark .token.token.tab:not(:empty):before,.md-code-block.md-code-block-dark .token.token.cr:before,.md-code-block.md-code-block-dark .token.token.lf:before,.md-code-block.md-code-block-dark .token.token.space:before{color:#8da1b9}.md-code-block.md-code-block-dark div.code-toolbar>.toolbar.toolbar>.toolbar-item>a,.md-code-block.md-code-block-dark div.code-toolbar>.toolbar.toolbar>.toolbar-item>button{color:#111b27;background:#6cb8e6}.md-code-block.md-code-block-dark div.code-toolbar>.toolbar.toolbar>.toolbar-item>a:hover,.md-code-block.md-code-block-dark div.code-toolbar>.toolbar.toolbar>.toolbar-item>a:focus,.md-code-block.md-code-block-dark div.code-toolbar>.toolbar.toolbar>.toolbar-item>button:hover,.md-code-block.md-code-block-dark div.code-toolbar>.toolbar.toolbar>.toolbar-item>button:focus{color:#111b27;background:#6cb8e6da;text-decoration:none}.md-code-block.md-code-block-dark div.code-toolbar>.toolbar.toolbar>.toolbar-item>span,.md-code-block.md-code-block-dark div.code-toolbar>.toolbar.toolbar>.toolbar-item>span:hover,.md-code-block.md-code-block-dark div.code-toolbar>.toolbar.toolbar>.toolbar-item>span:focus{color:#111b27;background:#8da1b9}.md-code-block.md-code-block-dark .line-highlight.line-highlight{background:linear-gradient(90deg,#3c526d5f 70%,#3c526d55)}.md-code-block.md-code-block-dark .line-highlight.line-highlight:before,.md-code-block.md-code-block-dark .line-highlight.line-highlight[data-end]:after{color:#111b27;background-color:#8da1b9;box-shadow:0 1px #3c526d}.md-code-block.md-code-block-dark pre[id].linkable-line-numbers.linkable-line-numbers span.line-numbers-rows>span:hover:before{background-color:#8da1b918}.md-code-block.md-code-block-dark .line-numbers.line-numbers .line-numbers-rows{background:#0b121b7a;border-right:1px solid #0b121b}.md-code-block.md-code-block-dark .line-numbers .line-numbers-rows>span:before{color:#8da1b9da}.md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-1,.md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-5,.md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-9{color:#e6d37a}.md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-2,.md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-6,.md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-10{color:#f4adf4}.md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-3,.md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-7,.md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-11{color:#6cb8e6}.md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-4,.md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-8,.md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-12{color:#c699e3}.md-code-block.md-code-block-dark pre.diff-highlight>code .token.token.deleted:not(.prefix),.md-code-block.md-code-block-dark pre>code.diff-highlight .token.token.deleted:not(.prefix){background-color:#cd66601f}.md-code-block.md-code-block-dark pre.diff-highlight>code .token.token.inserted:not(.prefix),.md-code-block.md-code-block-dark pre>code.diff-highlight .token.token.inserted:not(.prefix){background-color:#91d0761f}.md-code-block.md-code-block-dark .command-line .command-line-prompt{border-right:1px solid #0b121b}.md-code-block.md-code-block-dark .command-line .command-line-prompt>span:before{color:#8da1b9da}.md-code-block.md-code-block-light code[class*=language-],.md-code-block.md-code-block-light pre[class*=language-]{color:#383a42;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;-moz-tab-size:2;tab-size:2;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none;direction:ltr;background:#fafafa;font-family:Fira Code,Fira Mono,Menlo,Consolas,DejaVu Sans Mono,monospace;line-height:1.5}.md-code-block.md-code-block-light code[class*=language-]::-moz-selection{color:inherit;background:#e5e5e6}.md-code-block.md-code-block-light code[class*=language-]::selection{color:inherit;background:#e5e5e6}.md-code-block.md-code-block-light code[class*=language-] ::-moz-selection{color:inherit;background:#e5e5e6}.md-code-block.md-code-block-light code[class*=language-] ::selection{color:inherit;background:#e5e5e6}.md-code-block.md-code-block-light pre[class*=language-] ::-moz-selection{color:inherit;background:#e5e5e6}.md-code-block.md-code-block-light pre[class*=language-] ::selection{color:inherit;background:#e5e5e6}.md-code-block.md-code-block-light pre[class*=language-]{border-radius:.3em;margin:.5em 0;padding:1em;overflow:auto}.md-code-block.md-code-block-light :not(pre)>code[class*=language-]{white-space:normal;border-radius:.3em;padding:.2em .3em}.md-code-block.md-code-block-light .token.comment,.md-code-block.md-code-block-light .token.prolog,.md-code-block.md-code-block-light .token.cdata{color:#a0a1a7}.md-code-block.md-code-block-light .token.doctype,.md-code-block.md-code-block-light .token.punctuation,.md-code-block.md-code-block-light .token.entity{color:#383a42}.md-code-block.md-code-block-light .token.attr-name,.md-code-block.md-code-block-light .token.class-name,.md-code-block.md-code-block-light .token.boolean,.md-code-block.md-code-block-light .token.constant,.md-code-block.md-code-block-light .token.number,.md-code-block.md-code-block-light .token.atrule{color:#b76b01}.md-code-block.md-code-block-light .token.keyword{color:#a626a4}.md-code-block.md-code-block-light .token.property,.md-code-block.md-code-block-light .token.tag,.md-code-block.md-code-block-light .token.symbol,.md-code-block.md-code-block-light .token.deleted,.md-code-block.md-code-block-light .token.important{color:#e45649}.md-code-block.md-code-block-light .token.selector,.md-code-block.md-code-block-light .token.string,.md-code-block.md-code-block-light .token.char,.md-code-block.md-code-block-light .token.builtin,.md-code-block.md-code-block-light .token.inserted,.md-code-block.md-code-block-light .token.regex,.md-code-block.md-code-block-light .token.attr-value,.md-code-block.md-code-block-light .token.attr-value>.token.punctuation{color:#50a14f}.md-code-block.md-code-block-light .token.variable,.md-code-block.md-code-block-light .token.operator,.md-code-block.md-code-block-light .token.function{color:#4078f2}.md-code-block.md-code-block-light .token.url{color:#0184bc}.md-code-block.md-code-block-light .token.attr-value>.token.punctuation.attr-equals,.md-code-block.md-code-block-light .token.special-attr>.token.attr-value>.token.value.css{color:#383a42}.md-code-block.md-code-block-light .language-css .token.selector{color:#e45649}.md-code-block.md-code-block-light .language-css .token.property{color:#383a42}.md-code-block.md-code-block-light .language-css .token.function,.md-code-block.md-code-block-light .language-css .token.url>.token.function{color:#0184bc}.md-code-block.md-code-block-light .language-css .token.url>.token.string.url{color:#50a14f}.md-code-block.md-code-block-light .language-css .token.important,.md-code-block.md-code-block-light .language-css .token.atrule .token.rule,.md-code-block.md-code-block-light .language-javascript .token.operator{color:#a626a4}.md-code-block.md-code-block-light .language-javascript .token.template-string>.token.interpolation>.token.interpolation-punctuation.punctuation{color:#ca1243}.md-code-block.md-code-block-light .language-json .token.operator{color:#383a42}.md-code-block.md-code-block-light .language-json .token.null.keyword{color:#b76b01}.md-code-block.md-code-block-light .language-markdown .token.url,.md-code-block.md-code-block-light .language-markdown .token.url>.token.operator,.md-code-block.md-code-block-light .language-markdown .token.url-reference.url>.token.string{color:#383a42}.md-code-block.md-code-block-light .language-markdown .token.url>.token.content{color:#4078f2}.md-code-block.md-code-block-light .language-markdown .token.url>.token.url,.md-code-block.md-code-block-light .language-markdown .token.url-reference.url{color:#0184bc}.md-code-block.md-code-block-light .language-markdown .token.blockquote.punctuation,.md-code-block.md-code-block-light .language-markdown .token.hr.punctuation{color:#a0a1a7;font-style:italic}.md-code-block.md-code-block-light .language-markdown .token.code-snippet{color:#50a14f}.md-code-block.md-code-block-light .language-markdown .token.bold .token.content{color:#b76b01}.md-code-block.md-code-block-light .language-markdown .token.italic .token.content{color:#a626a4}.md-code-block.md-code-block-light .language-markdown .token.strike .token.content,.md-code-block.md-code-block-light .language-markdown .token.strike .token.punctuation,.md-code-block.md-code-block-light .language-markdown .token.list.punctuation,.md-code-block.md-code-block-light .language-markdown .token.title.important>.token.punctuation{color:#e45649}.md-code-block.md-code-block-light .token.bold{font-weight:700}.md-code-block.md-code-block-light .token.comment,.md-code-block.md-code-block-light .token.italic{font-style:italic}.md-code-block.md-code-block-light .token.entity{cursor:help}.md-code-block.md-code-block-light .token.namespace{opacity:.8}.md-code-block.md-code-block-light .token.token.tab:not(:empty):before,.md-code-block.md-code-block-light .token.token.cr:before,.md-code-block.md-code-block-light .token.token.lf:before,.md-code-block.md-code-block-light .token.token.space:before{color:#383a4233}.md-code-block-light{--ds-md-code-block-border-color: #e5e5e5;--ds-md-code-block-banner-wrap-background-color: white;--ds-md-code-banner-background-color: #f5f5f5;color:#494949;background:#fafafa}.md-code-block-dark{--ds-md-code-block-border-color: var(--dsr-bg, transparent);--ds-md-code-block-banner-wrap-background-color: #292a2d;--ds-md-code-banner-background-color: #404045;color:#fff;background:#212327}.md-code-block{--ds-md-code-block-border-radius: 12px;--ds-md-code-block-font-size: calc(var(--ds-md-zoom) * var(--ds-font-size-xsp));border-radius:var(--ds-md-code-block-border-radius);font-size:var(--ds-md-code-block-font-size);line-height:calc(var(--ds-md-code-block-font-size) * 1.6)}.md-code-block:not(:last-child){margin-bottom:calc(var(--ds-md-zoom) * 10px)}.md-code-block-banner-wrap{background-color:var(--ds-md-code-block-banner-wrap-background-color);z-index:6;position:-webkit-sticky;position:sticky;top:0}.md-code-block-banner{background:var(--ds-md-code-banner-background-color);padding:calc(var(--ds-md-zoom) * 8px) calc(var(--ds-md-zoom) * 12px);font-size:var(--ds-md-code-block-font-size);line-height:var(--ds-md-code-block-font-size);justify-content:space-between;display:flex}.md-code-block-banner-lite{padding:0}.md-code-block-banner{border-top-left-radius:var(--ds-md-code-block-border-radius);border-top-right-radius:var(--ds-md-code-block-border-radius)}.md-code-block-footer{background:var(--ds-md-code-banner-background-color);padding:calc(var(--ds-md-zoom) * 8px) calc(var(--ds-md-zoom) * 12px);font-size:var(--ds-md-code-block-font-size);line-height:var(--ds-md-code-block-font-size);border-bottom-left-radius:var(--ds-md-code-block-border-radius);border-bottom-right-radius:var(--ds-md-code-block-border-radius);justify-content:flex-end;display:flex}.md-code-block-action{align-items:center;display:flex}.md-code-block>pre{padding:calc(var(--ds-md-zoom) * 8px) calc(var(--ds-md-zoom) * 12px);white-space:pre-wrap;word-break:break-all;margin:0!important}:root{--message-list-padding-horizontal: 16px;--message-list-max-width: 800px}html,body{margin:0;padding:0;box-sizing:border-box;height:100%;overflow:hidden}#root{height:100%;overflow:hidden}.ds-message{height:100%;overflow:auto;display:flex;flex-direction:column}.ds-message-box{flex:1;overflow:auto}.ds-message-list{max-width:var(--message-list-max-width);margin:0 auto;padding:var(--message-list-padding-horizontal)}@media (min-width: 800px){.ds-message-actions{width:var(--message-list-max-width)}}.ds-message-actions{padding:30px;max-width:var(--message-list-max-width);padding:var(--message-list-padding-horizontal);margin:0 auto}.ds-message-actions button{padding:10px 20px;border-radius:5px;border:1px solid #1890ff;background-color:#1890ff;cursor:pointer;color:#fff;font-size:20px;font-weight:600} 2 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ds-markdown 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import globals from 'globals'; 3 | import reactHooks from 'eslint-plugin-react-hooks'; 4 | import reactRefresh from 'eslint-plugin-react-refresh'; 5 | import tseslint from 'typescript-eslint'; 6 | import eslintConfigPrettier from 'eslint-config-prettier/flat'; 7 | 8 | export default tseslint.config( 9 | { ignores: ['dist'] }, 10 | { 11 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 12 | files: ['**/*.{ts,tsx}'], 13 | languageOptions: { 14 | ecmaVersion: 2020, 15 | globals: globals.browser, 16 | }, 17 | plugins: { 18 | 'react-hooks': reactHooks, 19 | 'react-refresh': reactRefresh, 20 | }, 21 | rules: { 22 | ...reactHooks.configs.recommended.rules, 23 | 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }], 24 | 'no-unused-vars': 'warn', 25 | '@typescript-eslint/no-unused-vars': 'warn', 26 | }, 27 | }, 28 | eslintConfigPrettier, 29 | ); 30 | -------------------------------------------------------------------------------- /example/basic/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "message_id": 2, 3 | "parent_id": 1, 4 | "model": "", 5 | "role": "ASSISTANT", 6 | "content": "React 19 是 React 生态的重要更新,引入了多项改进和新特性,旨在简化开发流程、优化性能并增强功能。以下是 React 19 的主要新特性:\n\n---\n\n### 1. **React 编译器(React Compiler)**\n - **自动优化组件**:通过静态分析和动态跟踪,自动对组件进行记忆化(memoization),减少不必要的重新渲染。\n - **无需手动优化**:开发者不再需要手动使用 `useMemo` 或 `useCallback`,编译器会智能处理依赖项。\n\n---\n\n### 2. **服务端组件(Server Components)正式支持**\n - **服务端渲染优化**:直接在服务端渲染组件,减少客户端加载的代码量,提升首屏性能。\n - **无缝数据获取**:服务端组件可直接访问数据库或 API,简化数据获取流程。\n - **渐进式集成**:支持与服务端框架(如 Next.js、Remix)深度集成。\n\n---\n\n### 3. **动作处理(Actions)**\n - **简化表单处理**:通过 `useActionState` 和 `useFormStatus` 等新 Hook,统一管理表单提交状态和结果。\n - **示例代码**:\n ```jsx\n function Form({ action }) {\n const [state, formAction] = useActionState(action, null);\n return (\n
\n \n {state &&

{state.message}

}\n
\n );\n }\n ```\n\n---\n\n### 4. **文档元数据支持**\n - **SEO 优化**:直接在组件内使用 ``、`<meta>` 等标签管理文档元数据,无需第三方库。\n - **示例**:\n ```jsx\n function Page() {\n return (\n <>\n <title>React 19 新特性\n \n \n );\n }\n ```\n\n---\n\n### 5. **资源加载优化**\n - **异步资源预加载**:通过 `preload`、`preinit` 等 API 提前加载脚本或样式,减少页面阻塞。\n - **代码示例**:\n ```jsx\n import { prefetchDNS, preconnect, preload } from 'react-dom';\n prefetchDNS(\"https://example.com\");\n preload(\"https://example.com/style.css\", { as: \"style\" });\n ```\n\n---\n\n### 6. **增强的 Hooks**\n - **`use` Hook**:支持在条件语句或循环中使用异步操作,简化数据获取逻辑。\n ```jsx\n const data = use(fetchData());\n ```\n - **`useOptimistic`**:乐观更新 UI,适用于实时应用(如聊天、投票)。\n ```jsx\n const [optimisticState, addOptimistic] = useOptimistic(\n state,\n (currentState, optimisticValue) => [...currentState, optimisticValue]\n );\n ```\n\n---\n\n### 7. **错误处理改进**\n - **服务端错误恢复**:服务端组件渲染出错时,客户端可自动尝试重新渲染,避免页面崩溃。\n - **更清晰的错误边界**:通过 `errorBoundary` 属性指定错误处理组件。\n\n---\n\n### 8. **兼容性调整**\n - **弃用 `react-dom/createRoot`**:推荐使用新的根 API `react-dom/client`。\n - **移除旧版 API**:如 `ReactDOM.render` 和 `react-dom/server` 的遗留方法。\n\n---\n\n### 升级建议\n- **测试环境验证**:在非生产环境充分测试现有代码,尤其是涉及服务端渲染和表单处理的逻辑。\n- **关注官方迁移指南**:React 官网通常会提供详细的版本迁移说明。\n\n---\n\nReact 19 的这些改进显著降低了开发复杂性,同时提升了性能和用户体验。建议开发者参考 [React 官方博客](https://react.dev/blog) 获取最新动态和详细文档。", 7 | "thinking_enabled": true, 8 | "thinking_content": "好的,我现在需要回答用户关于React 19有哪些新特性的问题。首先,我得回想一下React的最新版本和官方发布的信息。React的版本号有时候有点让人困惑,因为用户提到的React 19可能指的是尚未发布或者刚刚发布的版本。我记得之前React 18是在2022年发布的,引入了并发特性。现在可能React 19已经进入测试阶段或者已经发布,但需要确认。\n\n接下来,我应该检查官方文档或React团队的官方公告,看看React 19的新特性有哪些。如果官方文档还没有更新,可能需要查看Beta版或Canary版的发布说明。比如,React的更新通常会先发布在Canary通道,供开发者测试。\n\n然后,我需要列出React 19的主要新特性。根据之前的社区讨论和RFC(Request for Comments)提案,可能包括自动批处理改进、新的钩子、服务器组件支持、文档元数据支持、资源加载优化、动作处理改进、错误处理增强等。比如,React Server Components可能已经稳定,并成为正式版的一部分。另外,React可能会引入use钩子来处理异步操作,简化数据获取。\n\n另外,需要注意React 19是否引入了破坏性变更,或者是否有弃用的功能。例如,可能移除了某些旧的API,或者改变了生命周期方法的行为。需要提醒用户注意这些变化,以便升级时做好准备。\n\n可能还需要提到一些性能方面的改进,比如更高效的渲染机制,或者与React Compiler相关的优化,自动处理组件的记忆化,减少不必要的重新渲染。\n\n然后,我需要将这些信息组织成结构化的回答,分点列出主要特性,每个特性简要说明其作用和好处。同时,确保信息准确,避免猜测,不确定的部分需要注明可能还在测试阶段或需要参考官方文档。\n\n最后,提醒用户由于React 19可能尚未正式发布,部分特性可能变更,建议关注官方发布说明。另外,如果用户现在使用的是旧版本,可能需要了解升级路径和注意事项。", 9 | "thinking_elapsed_secs": 19, 10 | "ban_edit": false, 11 | "ban_regenerate": false, 12 | "status": "FINISHED", 13 | "accumulated_token_usage": 868, 14 | "files": [], 15 | "tips": [ 16 | { 17 | "type": "warning", 18 | "position": "bottom", 19 | "content": "本回答由 AI 生成,内容仅供参考,请仔细甄别。" 20 | } 21 | ], 22 | "inserted_at": 1746086789.538057, 23 | "search_enabled": false, 24 | "search_status": null, 25 | "search_results": null 26 | } 27 | -------------------------------------------------------------------------------- /example/basic/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode, useCallback, useEffect, useMemo, useRef, useState } from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | 4 | import Markdown from '../../src'; 5 | import json from './data.json'; 6 | 7 | function throttle(fn: (...args: any[]) => void, delay: number) { 8 | let lastTime = 0; 9 | return (...args: unknown[]) => { 10 | const now = Date.now(); 11 | if (now - lastTime > delay) { 12 | fn(...args); 13 | lastTime = now; 14 | } 15 | }; 16 | } 17 | 18 | const BasicDemo = () => { 19 | const [thinkingContent, setThinkingContent] = useState(''); 20 | const [answerContent, setAnswerContent] = useState(''); 21 | const messageDivRef = useRef(null!); 22 | 23 | const scrollCacheRef = useRef<{ 24 | type: 'manual' | 'auto'; 25 | needAutoScroll: boolean; 26 | prevScrollTop: number; 27 | }>({ 28 | type: 'manual', 29 | needAutoScroll: true, 30 | prevScrollTop: 0, 31 | }); 32 | 33 | const onClick = () => { 34 | setThinkingContent(json.thinking_content); 35 | }; 36 | const onReset = () => { 37 | setThinkingContent(''); 38 | setAnswerContent(''); 39 | }; 40 | 41 | const throttleOnTypedChar = useMemo(() => { 42 | return throttle(() => { 43 | if (!scrollCacheRef.current.needAutoScroll) return; 44 | const messageDiv = messageDivRef.current; 45 | // 自动滑动到最底部 46 | if (messageDiv) { 47 | messageDiv.scrollTo({ 48 | top: messageDiv.scrollHeight, 49 | behavior: 'smooth', 50 | }); 51 | } 52 | }, 50); 53 | }, []); 54 | 55 | const onScroll = useMemo(() => { 56 | return throttle((e: React.UIEvent) => { 57 | // 如果是往上滚动,则说明是手动滚动,则需要停止自动向下滚动 58 | // console.log(e.currentTarget.scrollTop - scrollCacheRef.current.prevScrollTop); 59 | if (e.currentTarget.scrollTop < scrollCacheRef.current.prevScrollTop) { 60 | scrollCacheRef.current.needAutoScroll = false; 61 | } 62 | scrollCacheRef.current.prevScrollTop = e.currentTarget.scrollTop; 63 | }, 50); 64 | }, []); 65 | 66 | return ( 67 |
68 |
69 | {thinkingContent ? : } React 19有哪些新特性 70 |
71 |
72 |
73 | { 77 | // console.log('思考完成', args); 78 | if (thinkingContent) { 79 | setAnswerContent(json.content); 80 | } 81 | }} 82 | // onStart={(args) => { 83 | // console.log('思考开始', args); 84 | // }} 85 | // onTypedChar={(args) => { 86 | // console.log('打字中', args); 87 | // }} 88 | onTypedChar={throttleOnTypedChar} 89 | > 90 | {thinkingContent} 91 | 92 | 93 | {answerContent && ( 94 | { 98 | // console.log('思考完成', args); 99 | // if (thinkingContent) { 100 | // setAnswerContent(json.content); 101 | // } 102 | // }} 103 | // onStart={(args) => { 104 | // console.log('思考开始', args); 105 | // }} 106 | // onTypedChar={(args) => { 107 | // console.log('打字中', args); 108 | // }} 109 | onTypedChar={throttleOnTypedChar} 110 | > 111 | {answerContent} 112 | 113 | )} 114 |
115 |
116 |
117 | ); 118 | }; 119 | 120 | export default BasicDemo; 121 | -------------------------------------------------------------------------------- /example/cdm/define.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export enum CozeMessageEnum { 4 | Created = 'event:conversation.chat.created', 5 | InProgress = 'event:conversation.chat.in_progress', 6 | Delta = 'event:conversation.message.delta', 7 | MessageCompleted = 'event:conversation.message.completed', 8 | ChatCompleted = 'event:conversation.chat.completed', 9 | } 10 | 11 | 12 | export interface CozeResultI { 13 | content?: string; 14 | usage?: { 15 | token_count: number; 16 | output_count: number; 17 | input_count: number; 18 | }; 19 | completed_at?: number; 20 | created_at?: number; 21 | } 22 | 23 | export interface CozeMessageDataI extends CozeResultI { 24 | id: string; 25 | conversation_id: string; 26 | bot_id: string; 27 | last_error: { code: number; msg: string }; 28 | status: string; 29 | role?: 'user' | 'assistant'; 30 | 31 | /** 32 | * 推理思考过程 33 | */ 34 | reasoning_content?: string; 35 | type?: 'answer'; 36 | 37 | } 38 | 39 | export interface CozeMessageI { 40 | type: CozeMessageEnum; 41 | data: CozeMessageDataI; 42 | } 43 | -------------------------------------------------------------------------------- /example/cdm/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | // 导入 ./cozeData.text 4 | 5 | import cozeData from './cozeData'; 6 | 7 | interface CMDDemoProps { 8 | id?: number; 9 | } 10 | 11 | const modulePrefix = 'CMDDemo'; 12 | const CMDDemo: React.FC = (props: CMDDemoProps) => { 13 | return
; 14 | }; 15 | 16 | export default CMDDemo; 17 | -------------------------------------------------------------------------------- /example/cdm/util.ts: -------------------------------------------------------------------------------- 1 | import { CozeMessageEnum, CozeMessageI } from './define'; 2 | 3 | export function parseSSEData(rawData: string) { 4 | const events: CozeMessageI[] = []; 5 | const lines = rawData.split('\n'); 6 | let currentEvent: Partial = {}; 7 | 8 | for (const line of lines) { 9 | const trimmedLine = line.trim(); 10 | if (!trimmedLine) continue; // 忽略空行 11 | 12 | // 解析 event 行 13 | if (trimmedLine.startsWith('event:')) { 14 | currentEvent.type = trimmedLine.trim() as CozeMessageEnum; 15 | } else if (trimmedLine.startsWith('data:')) { 16 | // 解析 data 行 17 | try { 18 | const jsonString = trimmedLine.slice(5).trim(); 19 | currentEvent.data = JSON.parse(jsonString); 20 | events.push(currentEvent as CozeMessageI); // 保存完整事件 21 | currentEvent = {}; // 重置临时对象 22 | } catch (e) { 23 | console.error('JSON 解析失败:', e); 24 | } 25 | } 26 | } 27 | 28 | return events; 29 | } 30 | -------------------------------------------------------------------------------- /example/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --message-list-padding-horizontal: 16px; 3 | --message-list-max-width: 800px; 4 | } 5 | 6 | html, 7 | body { 8 | margin: 0; 9 | padding: 0; 10 | box-sizing: border-box; 11 | height: 100%; 12 | overflow: hidden; 13 | } 14 | 15 | #root { 16 | height: 100%; 17 | overflow: hidden; 18 | } 19 | 20 | .ds-message { 21 | height: 100%; 22 | overflow: auto; 23 | display: flex; 24 | flex-direction: column; 25 | } 26 | 27 | .ds-message-box { 28 | flex: 1; 29 | overflow: auto; 30 | } 31 | 32 | .ds-message-list { 33 | max-width: var(--message-list-max-width); 34 | margin: 0 auto; 35 | padding: var(--message-list-padding-horizontal); 36 | } 37 | 38 | @media (min-width: 800px) { 39 | .ds-message-actions { 40 | width: var(--message-list-max-width); 41 | } 42 | } 43 | 44 | .ds-message-actions { 45 | padding: 30px; 46 | max-width: var(--message-list-max-width); 47 | padding: var(--message-list-padding-horizontal); 48 | margin: 0 auto; 49 | button { 50 | padding: 10px 20px; 51 | border-radius: 5px; 52 | border: 1px solid #1890ff; 53 | background-color: #1890ff; 54 | cursor: pointer; 55 | color: #fff; 56 | font-size: 20px; 57 | font-weight: 600; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /example/index.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode, useCallback, useEffect, useMemo, useRef, useState } from 'react'; 2 | import { createRoot } from 'react-dom/client'; 3 | 4 | import '../src/style.less'; 5 | import './index.css'; 6 | 7 | import BasicDemo from './basic'; 8 | const App = () => { 9 | return ; 10 | }; 11 | 12 | createRoot(document.getElementById('root')!).render( 13 | 14 | 15 | , 16 | ); 17 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | ds-markdown 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) onshinpei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ds-markdown", 3 | "private": false, 4 | "version": "0.0.9", 5 | "main": "./dist/cjs/index.js", 6 | "types": "./dist/cjs/index.d.ts", 7 | "module": "./dist/esm/index.js", 8 | "style": "./src/style.css", 9 | "type": "module", 10 | "license": "MIT", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/onshinpei/ds-markdown" 14 | }, 15 | "bugs": { 16 | "url": "https://github.com/onshinpei/ds-markdown/issues" 17 | }, 18 | "exports": { 19 | ".": { 20 | "import": { 21 | "types": "./dist/esm/index.d.ts", 22 | "default": "./dist/esm/index.js" 23 | }, 24 | "require": { 25 | "types": "./dist/cjs/index.d.ts", 26 | "default": "./dist/cjs/index.js" 27 | } 28 | }, 29 | "./style.css": "./dist/style.css" 30 | }, 31 | "files": [ 32 | "dist" 33 | ], 34 | "scripts": { 35 | "dev": "vite", 36 | "build:docs": "rimraf docs && vite build", 37 | "build": "rimraf dist &&npm run build:cjs && npm run build:esm && npm run build:less", 38 | "build:cjs": "tsc --project tsconfig-cjs.json && echo '{ \"type\": \"commonjs\" }' > dist/cjs/package.json", 39 | "build:esm": "tsc --project tsconfig-esm.json", 40 | "build:less": "lessc src/style.less dist/style.css", 41 | "lint": "eslint .", 42 | "preview": "vite preview", 43 | "release": "npm run build && node scripts/set-tag.cjs && cross-env RELEASE_MODE=true npm publish", 44 | "prepublishOnly": "node scripts/prepublish.js" 45 | }, 46 | "dependencies": { 47 | "classnames": "^2.5.1", 48 | "react-markdown": "^10.1.0", 49 | "react-syntax-highlighter": "^15.6.1", 50 | "remark-gfm": "^4.0.1" 51 | }, 52 | "devDependencies": { 53 | "@antfu/eslint-config": "^4.12.0", 54 | "@babel/core": "^7.27.1", 55 | "@babel/preset-react": "^7.27.1", 56 | "@babel/preset-typescript": "^7.27.1", 57 | "@eslint-react/eslint-plugin": "^1.48.5", 58 | "@eslint/js": "^9.25.0", 59 | "@rollup/plugin-babel": "^6.0.4", 60 | "@rollup/plugin-commonjs": "^28.0.3", 61 | "@rollup/plugin-node-resolve": "^16.0.1", 62 | "@rollup/plugin-typescript": "^12.1.2", 63 | "@testing-library/jest-dom": "^6.6.3", 64 | "@types/jest": "^29.5.14", 65 | "@types/react": "^19.1.2", 66 | "@types/react-dom": "^19.1.2", 67 | "@types/react-syntax-highlighter": "^15.5.13", 68 | "@vitejs/plugin-react": "^4.4.1", 69 | "cross-env": "^7.0.3", 70 | "eslint": "^9.25.0", 71 | "eslint-config-prettier": "^10.1.2", 72 | "eslint-plugin-compat": "^6.0.2", 73 | "eslint-plugin-prettier": "^5.3.1", 74 | "eslint-plugin-react": "^7.37.5", 75 | "eslint-plugin-react-hooks": "^5.2.0", 76 | "eslint-plugin-react-refresh": "^0.4.19", 77 | "globals": "^16.0.0", 78 | "less": "^4.3.0", 79 | "prettier": "3.5.3", 80 | "react": "^19.1.0", 81 | "react-dom": "^19.1.0", 82 | "react-refresh": "^0.17.0", 83 | "rehype-stringify": "^10.0.1", 84 | "remark-parse": "^11.0.0", 85 | "remark-rehype": "^11.1.2", 86 | "rimraf": "^6.0.1", 87 | "rollup": "^4.40.2", 88 | "rollup-plugin-postcss": "^4.0.2", 89 | "typescript": "~5.8.3", 90 | "typescript-eslint": "^8.30.1", 91 | "unified": "^11.0.5", 92 | "vite": "^6.3.2" 93 | }, 94 | "peerDependencies": { 95 | "react": ">=16.8.0" 96 | }, 97 | "keywords": [ 98 | "markdown", 99 | "deepseek", 100 | "deepseek-markdown", 101 | "deepseek-markdown-react", 102 | "react", 103 | "react-component", 104 | "react-markdown" 105 | ], 106 | "publishConfig": { 107 | "tag": "latest" 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | printWidth: 200, 3 | tabWidth: 2, 4 | useTabs: false, 5 | singleQuote: true, 6 | }; 7 | -------------------------------------------------------------------------------- /scripts/prepublish.js: -------------------------------------------------------------------------------- 1 | const RELEASE_MODE = !!process.env.RELEASE_MODE; 2 | 3 | if (!RELEASE_MODE) { 4 | console.log('Run `npm run release` to publish the package'); 5 | process.exit(1); 6 | } 7 | -------------------------------------------------------------------------------- /scripts/set-tag.cjs: -------------------------------------------------------------------------------- 1 | // scripts/set-tag.js 2 | const { version } = require('../package.json'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const { execSync } = require('child_process'); 6 | // 如果当前分支是main,则设置tag为latest,否则设置为beta 7 | 8 | const currentBranch = execSync('git branch --show-current').toString().trim(); 9 | 10 | const packagePath = path.join(__dirname, '../package.json'); 11 | const packageData = require(packagePath); 12 | 13 | if (currentBranch === 'main') { 14 | packageData.publishConfig = { tag: 'latest' }; 15 | // 如果读取到版本号位 0.0.7-beta.x,则latest版本号设置为0.0.7 16 | // 0.0.7-beta.x -> 0.0.7 17 | if (version.includes('beta')) { 18 | packageData.version = version.split('-')[0]; 19 | } else { 20 | packageData.version = version.split('.')[0] + '.' + parseInt(version.split('.')[1]) + '.' + (parseInt(version.split('.')[2]) + 1); 21 | } 22 | } else { 23 | packageData.publishConfig = { tag: 'beta' }; 24 | 25 | if (version.includes('beta')) { 26 | // 如果读取到版本号位 0.0.7-beta.0,则beta版本号设置为0.0.8-beta.1 27 | packageData.version = version.split('-')[0] + '-beta.' + (parseInt(version.split('-')[1].split('.')[1]) + 1); 28 | } else { 29 | // 如果读取到版本号位 0.0.7,则beta版本号设置为0.0.8-beta.0 30 | packageData.version = version.split('.')[0] + '.' + parseInt(version.split('.')[1]) + '.' + (parseInt(version.split('.')[2]) + 1) + '-beta.0'; 31 | } 32 | } 33 | 34 | fs.writeFileSync(packagePath, JSON.stringify(packageData, null, 2)); 35 | -------------------------------------------------------------------------------- /shim.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css' { 2 | const content: { [className: string]: string }; 3 | export default content; 4 | } 5 | 6 | declare module '*.less' { 7 | const content: { [className: string]: string }; 8 | export default content; 9 | } 10 | -------------------------------------------------------------------------------- /src/AutoScroll/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface AutoScrollProps { 4 | children: React.ReactNode; 5 | } 6 | 7 | const modulePrefix = 'AutoScroll'; 8 | const AutoScroll: React.FC = (props: AutoScrollProps) => { 9 | return
{props.children}
; 10 | }; 11 | 12 | export default AutoScroll; 13 | -------------------------------------------------------------------------------- /src/Markdown/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo, useEffect, useMemo, useRef } from 'react'; 2 | import { __DEV__ } from '../constant.js'; 3 | import { AnswerType, MarkdownProps } from '../defined.js'; 4 | import MarkdownCMD, { MarkdownRef } from '../MarkdownCMD/index.js'; 5 | 6 | interface MarkdownImplProps extends MarkdownProps { 7 | children: string | undefined; 8 | answerType: AnswerType; 9 | } 10 | 11 | const MarkdownInner: React.FC = ({ children: _children = '', answerType, ...rest }) => { 12 | const cmdRef = useRef(null!); 13 | const prefixRef = useRef(''); 14 | const content = useMemo(() => { 15 | if (typeof _children === 'string') { 16 | return _children; 17 | } 18 | if (__DEV__) { 19 | console.error('Markdown组件的子元素必须是一个字符串'); 20 | } 21 | return ''; 22 | }, [_children]); 23 | 24 | useEffect(() => { 25 | if (prefixRef.current !== content) { 26 | let newContent = ''; 27 | if (prefixRef.current === '') { 28 | newContent = content; 29 | } else { 30 | if (content.startsWith(prefixRef.current)) { 31 | newContent = content.slice(prefixRef.current.length); 32 | } else { 33 | newContent = content; 34 | cmdRef.current.clear(); 35 | } 36 | } 37 | cmdRef.current.push(newContent, answerType); 38 | prefixRef.current = content; 39 | } 40 | }, [answerType, content]); 41 | 42 | return ; 43 | }; 44 | 45 | const Markdown: React.FC = (props) => { 46 | const { children = '', answerType = 'answer' } = props; 47 | 48 | if (__DEV__) { 49 | if (!['thinking', 'answer'].includes(answerType)) { 50 | throw new Error('Markdown组件的answerType必须是thinking或answer'); 51 | } 52 | if (typeof children !== 'string') { 53 | throw new Error('Markdown组件的子元素必须是一个字符串'); 54 | } 55 | } 56 | 57 | return ; 58 | }; 59 | 60 | export default memo(Markdown); 61 | -------------------------------------------------------------------------------- /src/MarkdownCMD/index.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; 2 | 3 | import HighReactMarkdown from '../components/HighReactMarkdown/index.js'; 4 | import classNames from 'classnames'; 5 | import { AnswerType, IParagraph, MarkdownProps } from '../defined.js'; 6 | import { __DEV__ } from '../constant.js'; 7 | 8 | type MarkdownCMDProps = MarkdownProps; 9 | 10 | interface IChar { 11 | content: string; 12 | answerType: AnswerType; 13 | } 14 | 15 | export interface MarkdownRef { 16 | push: (content: string, answerType: AnswerType) => void; 17 | clear: () => void; 18 | triggerWholeEnd: () => void; 19 | } 20 | const MarkdownCMD = forwardRef(({ interval = 30, isClosePrettyTyped = false, onEnd, onStart, onTypedChar }, ref) => { 21 | /** 当前需要打字的内容 */ 22 | const charsRef = useRef([]); 23 | 24 | /** 25 | * 打字是否已经完全结束 26 | * 如果打字已经完全结束,则不会再触发打字效果 27 | */ 28 | const isWholeTypedEndRef = useRef(false); 29 | 30 | /** 已经打过的字 */ 31 | const typedCharsRef = useRef<{ typedContent: string; answerType: AnswerType; prevStr: string } | undefined>(undefined); 32 | /** 是否卸载 */ 33 | const isUnmountRef = useRef(false); 34 | /** 是否正在打字 */ 35 | const isTypedRef = useRef(false); 36 | 37 | /** 打字结束回调, */ 38 | const onEndRef = useRef(onEnd); 39 | onEndRef.current = onEnd; 40 | /** 打字开始回调 */ 41 | const onStartRef = useRef(onStart); 42 | onStartRef.current = onStart; 43 | /** 打字过程中回调 */ 44 | const onTypedCharRef = useRef(onTypedChar); 45 | onTypedCharRef.current = onTypedChar; 46 | 47 | /** 打字定时器 */ 48 | const timerRef = useRef(null); 49 | /** 50 | * 稳定段落 51 | * 稳定段落是已经打过字,并且不会再变化的段落 52 | */ 53 | const [stableParagraphs, setStableParagraphs] = useState([]); 54 | /** 当前段落 */ 55 | const [currentParagraph, setCurrentParagraph] = useState(undefined); 56 | /** 当前段落引用 */ 57 | const currentParagraphRef = useRef(undefined); 58 | currentParagraphRef.current = currentParagraph; 59 | 60 | /** 清除打字定时器 */ 61 | const clearTimer = () => { 62 | if (timerRef.current) { 63 | clearTimeout(timerRef.current); 64 | timerRef.current = null; 65 | } 66 | isTypedRef.current = false; 67 | }; 68 | 69 | useEffect(() => { 70 | isUnmountRef.current = false; 71 | return () => { 72 | isUnmountRef.current = true; 73 | }; 74 | }, []); 75 | 76 | /** 思考段落 */ 77 | const thinkingParagraphs = useMemo(() => stableParagraphs.filter((paragraph) => paragraph.answerType === 'thinking'), [stableParagraphs]); 78 | /** 回答段落 */ 79 | const answerParagraphs = useMemo(() => stableParagraphs.filter((paragraph) => paragraph.answerType === 'answer'), [stableParagraphs]); 80 | 81 | /** 82 | * 记录打过的字 83 | * @param char 当前字符 84 | * @returns 85 | */ 86 | const recordTypedChars = (char: IChar) => { 87 | let prevStr = ''; 88 | if (!typedCharsRef.current || typedCharsRef.current.answerType !== char.answerType) { 89 | typedCharsRef.current = { 90 | typedContent: char.content, 91 | answerType: char.answerType, 92 | prevStr: '', 93 | }; 94 | } else { 95 | prevStr = typedCharsRef.current.typedContent; 96 | typedCharsRef.current.typedContent += char.content; 97 | typedCharsRef.current.prevStr = prevStr; 98 | } 99 | 100 | return { 101 | prevStr, 102 | nextStr: typedCharsRef.current?.typedContent || '', 103 | }; 104 | }; 105 | 106 | /** 107 | * 触发打字开始回调 108 | * @param char 当前字符 109 | */ 110 | const triggerOnStart = (char: IChar) => { 111 | const onStartFn = onStartRef.current; 112 | if (!onStartFn) { 113 | return; 114 | } 115 | const { prevStr } = recordTypedChars(char); 116 | onStartRef.current?.({ 117 | currentIndex: prevStr.length, 118 | currentChar: char.content, 119 | answerType: char.answerType, 120 | prevStr, 121 | }); 122 | }; 123 | 124 | /** 125 | * 触发打字结束回调 126 | */ 127 | const triggerOnEnd = () => { 128 | const onEndFn = onEndRef.current; 129 | if (!onEndFn) { 130 | return; 131 | } 132 | 133 | onEndFn({ 134 | str: typedCharsRef.current?.typedContent, 135 | answerType: typedCharsRef.current?.answerType, 136 | }); 137 | }; 138 | 139 | /** 140 | * 触发打字过程中回调 141 | * @param char 当前字符 142 | * @param isStartPoint 是否是开始打字(第一个字) 143 | */ 144 | const triggerOnTypedChar = (char: IChar, isStartPoint = false) => { 145 | const onTypedCharFn = onTypedCharRef.current; 146 | if (!isStartPoint) { 147 | recordTypedChars(char); 148 | } 149 | if (!onTypedCharFn) { 150 | return; 151 | } 152 | 153 | onTypedCharFn({ 154 | currentIndex: typedCharsRef.current?.prevStr.length || 0, 155 | currentChar: char.content, 156 | answerType: char.answerType, 157 | prevStr: typedCharsRef.current?.prevStr || '', 158 | }); 159 | }; 160 | 161 | /** 开始打字任务 */ 162 | const startTypedTask = () => { 163 | if (isTypedRef.current) { 164 | return; 165 | } 166 | 167 | const chars = charsRef.current; 168 | 169 | /** 停止打字 */ 170 | const stopTyped = () => { 171 | isTypedRef.current = false; 172 | if (timerRef.current) { 173 | clearTimeout(timerRef.current); 174 | timerRef.current = null; 175 | } 176 | triggerOnEnd(); 177 | }; 178 | 179 | /** 打下一个字 */ 180 | const nextTyped = () => { 181 | if (chars.length === 0) { 182 | stopTyped(); 183 | return; 184 | } 185 | timerRef.current = setTimeout(startTyped, interval); 186 | }; 187 | 188 | /** 189 | * 开始打字 190 | * @param isStartPoint 是否是开始打字 191 | */ 192 | function startTyped(isStartPoint = false) { 193 | if (isUnmountRef.current) { 194 | return; 195 | } 196 | isTypedRef.current = true; 197 | 198 | const char = chars.shift(); 199 | if (char === undefined) { 200 | stopTyped(); 201 | return; 202 | } 203 | 204 | if (isStartPoint) { 205 | triggerOnStart(char); 206 | triggerOnTypedChar(char, isStartPoint); 207 | } else { 208 | triggerOnTypedChar(char); 209 | } 210 | 211 | const currentParagraph = currentParagraphRef.current; 212 | /** 如果碰到 则需要处理成两个段落 */ 213 | if (char.content === '\n\n') { 214 | if (currentParagraph) { 215 | setStableParagraphs((prev) => { 216 | const newParagraphs = [...prev]; 217 | if (currentParagraph) { 218 | newParagraphs.push({ ...currentParagraph, isTyped: false }); 219 | } 220 | newParagraphs.push({ 221 | content: '', 222 | isTyped: false, 223 | type: 'br', 224 | answerType: char.answerType, 225 | }); 226 | return newParagraphs; 227 | }); 228 | setCurrentParagraph(undefined); 229 | } else { 230 | setStableParagraphs((prev) => { 231 | const newParagraphs = [...prev]; 232 | newParagraphs.push({ 233 | content: '', 234 | isTyped: false, 235 | type: 'br', 236 | answerType: char.answerType, 237 | }); 238 | return newParagraphs; 239 | }); 240 | } 241 | nextTyped(); 242 | return; 243 | } 244 | 245 | // 处理当前段落 246 | let _currentParagraph = currentParagraph; 247 | const newCurrentParagraph: IParagraph = { 248 | content: '', 249 | isTyped: false, 250 | type: 'text', 251 | answerType: char.answerType, 252 | }; 253 | 254 | if (!_currentParagraph) { 255 | // 如果当前没有段落,则直接设置为当前段落 256 | _currentParagraph = newCurrentParagraph; 257 | } else if (currentParagraph && currentParagraph?.answerType !== char.answerType) { 258 | // 如果当前段落和当前字符的回答类型不一致,则需要处理成两个段落 259 | setStableParagraphs((prev) => { 260 | const newParagraphs = [...prev]; 261 | newParagraphs.push({ ...currentParagraph, isTyped: false }); 262 | return newParagraphs; 263 | }); 264 | _currentParagraph = newCurrentParagraph; 265 | setCurrentParagraph(_currentParagraph); 266 | } 267 | 268 | setCurrentParagraph((prev) => { 269 | return { 270 | ..._currentParagraph, 271 | content: (prev?.content || '') + char.content, 272 | isTyped: true, 273 | }; 274 | }); 275 | 276 | nextTyped(); 277 | } 278 | 279 | startTyped(true); 280 | }; 281 | 282 | useImperativeHandle(ref, () => ({ 283 | /** 284 | * 添加内容 285 | * @param content 内容 {string} 286 | * @param answerType 回答类型 {AnswerType} 287 | */ 288 | push: (content: string, answerType: AnswerType) => { 289 | if (isWholeTypedEndRef.current) { 290 | if (__DEV__) { 291 | console.warn('打字已经完全结束,不能再添加新的内容'); 292 | } 293 | return; 294 | } 295 | // 如果两个\n,则\n这两个字符要合一起,作为一个字符处理,并且把多个\n处理成一个\n 296 | const charsGroup = content.split('\n\n'); 297 | charsGroup.forEach((chars, index) => { 298 | if (isClosePrettyTyped) { 299 | charsRef.current.push({ content: chars, answerType }); 300 | } else { 301 | charsRef.current.push(...chars.split('').map((char) => ({ content: char, answerType }))); 302 | } 303 | if (index !== charsGroup.length - 1) { 304 | charsRef.current.push({ content: '\n\n', answerType }); 305 | } 306 | }); 307 | 308 | if (!isTypedRef.current) { 309 | startTypedTask(); 310 | } 311 | }, 312 | /** 313 | * 清除打字任务 314 | */ 315 | clear: () => { 316 | clearTimer(); 317 | charsRef.current = []; 318 | setStableParagraphs([]); 319 | setCurrentParagraph(undefined); 320 | isWholeTypedEndRef.current = false; 321 | }, 322 | /** 323 | * 主动触发打字结束 324 | */ 325 | triggerWholeEnd: () => { 326 | isWholeTypedEndRef.current = true; 327 | if (!isTypedRef.current) { 328 | triggerOnEnd(); 329 | } 330 | }, 331 | })); 332 | 333 | const getParagraphs = (paragraphs: IParagraph[], answerType: AnswerType) => { 334 | return ( 335 |
336 | {paragraphs.map((paragraph, index) => { 337 | if (paragraph.type === 'br') { 338 | return null; 339 | } 340 | return {paragraph.content || ''}; 341 | })} 342 | {currentParagraph?.answerType === answerType && {currentParagraph.content || ''}} 343 |
344 | ); 345 | }; 346 | 347 | return ( 348 |
354 | {(thinkingParagraphs.length > 0 || currentParagraph?.answerType === 'thinking') &&
{getParagraphs(thinkingParagraphs, 'thinking')}
} 355 | {(answerParagraphs.length > 0 || currentParagraph?.answerType === 'answer') &&
{getParagraphs(answerParagraphs, 'answer')}
} 356 |
357 | ); 358 | }); 359 | 360 | export default MarkdownCMD; 361 | -------------------------------------------------------------------------------- /src/components/BlockWrap/index.less: -------------------------------------------------------------------------------- 1 | .md-code-block { 2 | --ds-md-code-block-border-radius: 12px; 3 | --ds-md-code-block-font-size: calc(var(--ds-md-zoom) * var(--ds-font-size-xsp)); 4 | border-radius: var(--ds-md-code-block-border-radius); 5 | font-size: var(--ds-md-code-block-font-size); 6 | line-height: calc(var(--ds-md-code-block-font-size) * 1.6); 7 | } 8 | 9 | .md-code-block-light { 10 | --ds-md-code-block-border-color: #e5e5e5; 11 | --ds-md-code-block-banner-wrap-background-color: white; 12 | --ds-md-code-banner-background-color: #f5f5f5; 13 | color: #494949; 14 | background: #fafafa; 15 | } 16 | 17 | .md-code-block-banner { 18 | border-top-left-radius: var(--ds-md-code-block-border-radius); 19 | border-top-right-radius: var(--ds-md-code-block-border-radius); 20 | } 21 | 22 | .md-code-block-banner-lite { 23 | padding: 0; 24 | } 25 | 26 | .md-code-block-banner-content { 27 | justify-content: space-between; 28 | align-items: center; 29 | padding: 6px 14px 6px 6px; 30 | display: flex; 31 | } 32 | 33 | .md-code-block-language { 34 | color: var(--dsr-text-2); 35 | margin-left: 8px; 36 | font-size: 12px; 37 | line-height: 18px; 38 | } 39 | 40 | .md-code-block-content { 41 | } 42 | -------------------------------------------------------------------------------- /src/components/BlockWrap/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface BlockWrapProps { 4 | children: React.ReactNode; 5 | language: string; 6 | } 7 | 8 | const BlockWrap: React.FC = ({ children, language }) => { 9 | return ( 10 |
11 |
12 |
13 |
14 |
{language}
15 | {/*
复制
*/} 16 |
17 |
18 |
19 |
{children}
20 |
21 | ); 22 | }; 23 | 24 | export default BlockWrap; 25 | -------------------------------------------------------------------------------- /src/components/HighReactMarkdown/highlightTheme.less: -------------------------------------------------------------------------------- 1 | .md-code-block.md-code-block-dark code[class*='language-'], 2 | .md-code-block.md-code-block-dark pre[class*='language-'] { 3 | color: #e3eaf2; 4 | text-align: left; 5 | white-space: pre; 6 | word-spacing: normal; 7 | word-break: normal; 8 | word-wrap: normal; 9 | -moz-tab-size: 4; 10 | tab-size: 4; 11 | -webkit-hyphens: none; 12 | -ms-hyphens: none; 13 | hyphens: none; 14 | background: 0 0; 15 | font-family: 16 | Consolas, 17 | Monaco, 18 | Andale Mono, 19 | Ubuntu Mono, 20 | monospace; 21 | line-height: 1.5; 22 | } 23 | 24 | .md-code-block.md-code-block-dark pre[class*='language-']::-moz-selection { 25 | background: #3c526d; 26 | } 27 | 28 | .md-code-block.md-code-block-dark pre[class*='language-'] ::-moz-selection { 29 | background: #3c526d; 30 | } 31 | 32 | .md-code-block.md-code-block-dark code[class*='language-']::-moz-selection { 33 | background: #3c526d; 34 | } 35 | 36 | .md-code-block.md-code-block-dark code[class*='language-'] ::-moz-selection { 37 | background: #3c526d; 38 | } 39 | 40 | .md-code-block.md-code-block-dark pre[class*='language-']::-moz-selection { 41 | background: #3c526d; 42 | } 43 | 44 | .md-code-block.md-code-block-dark pre[class*='language-'] ::-moz-selection { 45 | background: #3c526d; 46 | } 47 | 48 | .md-code-block.md-code-block-dark code[class*='language-']::-moz-selection { 49 | background: #3c526d; 50 | } 51 | 52 | .md-code-block.md-code-block-dark code[class*='language-'] ::-moz-selection { 53 | background: #3c526d; 54 | } 55 | 56 | .md-code-block.md-code-block-dark pre[class*='language-']::-moz-selection { 57 | background: #3c526d; 58 | } 59 | 60 | .md-code-block.md-code-block-dark pre[class*='language-']::selection { 61 | background: #3c526d; 62 | } 63 | 64 | .md-code-block.md-code-block-dark pre[class*='language-'] ::-moz-selection { 65 | background: #3c526d; 66 | } 67 | 68 | .md-code-block.md-code-block-dark pre[class*='language-'] ::selection { 69 | background: #3c526d; 70 | } 71 | 72 | .md-code-block.md-code-block-dark code[class*='language-']::-moz-selection { 73 | background: #3c526d; 74 | } 75 | 76 | .md-code-block.md-code-block-dark code[class*='language-']::selection { 77 | background: #3c526d; 78 | } 79 | 80 | .md-code-block.md-code-block-dark code[class*='language-'] ::-moz-selection { 81 | background: #3c526d; 82 | } 83 | 84 | .md-code-block.md-code-block-dark code[class*='language-'] ::selection { 85 | background: #3c526d; 86 | } 87 | 88 | .md-code-block.md-code-block-dark pre[class*='language-'] { 89 | margin: 0.5em 0; 90 | padding: 1em; 91 | overflow: auto; 92 | } 93 | 94 | .md-code-block.md-code-block-dark :not(pre) > code[class*='language-'], 95 | .md-code-block.md-code-block-dark pre[class*='language-'] { 96 | background: #111b27; 97 | } 98 | 99 | .md-code-block.md-code-block-dark :not(pre) > code[class*='language-'] { 100 | white-space: normal; 101 | border-radius: 0.3em; 102 | padding: 0.1em 0.3em; 103 | } 104 | 105 | .md-code-block.md-code-block-dark .token.comment, 106 | .md-code-block.md-code-block-dark .token.prolog, 107 | .md-code-block.md-code-block-dark .token.doctype, 108 | .md-code-block.md-code-block-dark .token.cdata { 109 | color: #8da1b9; 110 | } 111 | 112 | .md-code-block.md-code-block-dark .token.punctuation { 113 | color: #e3eaf2; 114 | } 115 | 116 | .md-code-block.md-code-block-dark .token.delimiter.important, 117 | .md-code-block.md-code-block-dark .token.selector .parent, 118 | .md-code-block.md-code-block-dark .token.tag, 119 | .md-code-block.md-code-block-dark .token.tag .token.punctuation { 120 | color: #6cc; 121 | } 122 | 123 | .md-code-block.md-code-block-dark .token.attr-name, 124 | .md-code-block.md-code-block-dark .token.boolean, 125 | .md-code-block.md-code-block-dark .token.boolean.important, 126 | .md-code-block.md-code-block-dark .token.number, 127 | .md-code-block.md-code-block-dark .token.constant, 128 | .md-code-block.md-code-block-dark .token.selector .token.attribute { 129 | color: #e6d37a; 130 | } 131 | 132 | .md-code-block.md-code-block-dark .token.class-name, 133 | .md-code-block.md-code-block-dark .token.key, 134 | .md-code-block.md-code-block-dark .token.parameter, 135 | .md-code-block.md-code-block-dark .token.property, 136 | .md-code-block.md-code-block-dark .token.property-access, 137 | .md-code-block.md-code-block-dark .token.variable { 138 | color: #6cb8e6; 139 | } 140 | 141 | .md-code-block.md-code-block-dark .token.attr-value, 142 | .md-code-block.md-code-block-dark .token.inserted, 143 | .md-code-block.md-code-block-dark .token.color, 144 | .md-code-block.md-code-block-dark .token.selector .token.value, 145 | .md-code-block.md-code-block-dark .token.string, 146 | .md-code-block.md-code-block-dark .token.string .token.url-link { 147 | color: #91d076; 148 | } 149 | 150 | .md-code-block.md-code-block-dark .token.builtin, 151 | .md-code-block.md-code-block-dark .token.keyword-array, 152 | .md-code-block.md-code-block-dark .token.package, 153 | .md-code-block.md-code-block-dark .token.regex { 154 | color: #f4adf4; 155 | } 156 | 157 | .md-code-block.md-code-block-dark .token.function, 158 | .md-code-block.md-code-block-dark .token.selector .token.class, 159 | .md-code-block.md-code-block-dark .token.selector .token.id { 160 | color: #c699e3; 161 | } 162 | 163 | .md-code-block.md-code-block-dark .token.atrule .token.rule, 164 | .md-code-block.md-code-block-dark .token.combinator, 165 | .md-code-block.md-code-block-dark .token.keyword, 166 | .md-code-block.md-code-block-dark .token.operator, 167 | .md-code-block.md-code-block-dark .token.pseudo-class, 168 | .md-code-block.md-code-block-dark .token.pseudo-element, 169 | .md-code-block.md-code-block-dark .token.selector, 170 | .md-code-block.md-code-block-dark .token.unit { 171 | color: #e9ae7e; 172 | } 173 | 174 | .md-code-block.md-code-block-dark .token.deleted, 175 | .md-code-block.md-code-block-dark .token.important { 176 | color: #cd6660; 177 | } 178 | 179 | .md-code-block.md-code-block-dark .token.keyword-this, 180 | .md-code-block.md-code-block-dark .token.this { 181 | color: #6cb8e6; 182 | } 183 | 184 | .md-code-block.md-code-block-dark .token.important, 185 | .md-code-block.md-code-block-dark .token.keyword-this, 186 | .md-code-block.md-code-block-dark .token.this, 187 | .md-code-block.md-code-block-dark .token.bold { 188 | font-weight: 700; 189 | } 190 | 191 | .md-code-block.md-code-block-dark .token.delimiter.important { 192 | font-weight: inherit; 193 | } 194 | 195 | .md-code-block.md-code-block-dark .token.italic { 196 | font-style: italic; 197 | } 198 | 199 | .md-code-block.md-code-block-dark .token.entity { 200 | cursor: help; 201 | } 202 | 203 | .md-code-block.md-code-block-dark .language-markdown .token.title, 204 | .md-code-block.md-code-block-dark .language-markdown .token.title .token.punctuation { 205 | color: #6cb8e6; 206 | font-weight: 700; 207 | } 208 | 209 | .md-code-block.md-code-block-dark .language-markdown .token.blockquote.punctuation { 210 | color: #f4adf4; 211 | } 212 | 213 | .md-code-block.md-code-block-dark .language-markdown .token.code { 214 | color: #6cc; 215 | } 216 | 217 | .md-code-block.md-code-block-dark .language-markdown .token.hr.punctuation { 218 | color: #6cb8e6; 219 | } 220 | 221 | .md-code-block.md-code-block-dark .language-markdown .token.url .token.content { 222 | color: #91d076; 223 | } 224 | 225 | .md-code-block.md-code-block-dark .language-markdown .token.url-link { 226 | color: #e6d37a; 227 | } 228 | 229 | .md-code-block.md-code-block-dark .language-markdown .token.list.punctuation { 230 | color: #f4adf4; 231 | } 232 | 233 | .md-code-block.md-code-block-dark .language-markdown .token.table-header, 234 | .md-code-block.md-code-block-dark .language-json .token.operator { 235 | color: #e3eaf2; 236 | } 237 | 238 | .md-code-block.md-code-block-dark .language-scss .token.variable { 239 | color: #6cc; 240 | } 241 | 242 | .md-code-block.md-code-block-dark .token.token.tab:not(:empty):before, 243 | .md-code-block.md-code-block-dark .token.token.cr:before, 244 | .md-code-block.md-code-block-dark .token.token.lf:before, 245 | .md-code-block.md-code-block-dark .token.token.space:before { 246 | color: #8da1b9; 247 | } 248 | 249 | .md-code-block.md-code-block-dark div.code-toolbar > .toolbar.toolbar > .toolbar-item > a, 250 | .md-code-block.md-code-block-dark div.code-toolbar > .toolbar.toolbar > .toolbar-item > button { 251 | color: #111b27; 252 | background: #6cb8e6; 253 | } 254 | 255 | .md-code-block.md-code-block-dark div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:hover, 256 | .md-code-block.md-code-block-dark div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:focus, 257 | .md-code-block.md-code-block-dark div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:hover, 258 | .md-code-block.md-code-block-dark div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:focus { 259 | color: #111b27; 260 | background: rgba(108, 184, 230, 0.855); 261 | text-decoration: none; 262 | } 263 | 264 | .md-code-block.md-code-block-dark div.code-toolbar > .toolbar.toolbar > .toolbar-item > span, 265 | .md-code-block.md-code-block-dark div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:hover, 266 | .md-code-block.md-code-block-dark div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:focus { 267 | color: #111b27; 268 | background: #8da1b9; 269 | } 270 | 271 | .md-code-block.md-code-block-dark .line-highlight.line-highlight { 272 | background: linear-gradient(90deg, rgba(60, 82, 109, 0.373) 70%, rgba(60, 82, 109, 0.333)); 273 | } 274 | 275 | .md-code-block.md-code-block-dark .line-highlight.line-highlight:before, 276 | .md-code-block.md-code-block-dark .line-highlight.line-highlight[data-end]:after { 277 | color: #111b27; 278 | background-color: #8da1b9; 279 | box-shadow: 0 1px #3c526d; 280 | } 281 | 282 | .md-code-block.md-code-block-dark pre[id].linkable-line-numbers.linkable-line-numbers span.line-numbers-rows > span:hover:before { 283 | background-color: rgba(141, 161, 185, 0.094); 284 | } 285 | 286 | .md-code-block.md-code-block-dark .line-numbers.line-numbers .line-numbers-rows { 287 | background: rgba(11, 18, 27, 0.48); 288 | border-right: 1px solid #0b121b; 289 | } 290 | 291 | .md-code-block.md-code-block-dark .line-numbers .line-numbers-rows > span:before { 292 | color: rgba(141, 161, 185, 0.855); 293 | } 294 | 295 | .md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-1, 296 | .md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-5, 297 | .md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-9 { 298 | color: #e6d37a; 299 | } 300 | 301 | .md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-2, 302 | .md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-6, 303 | .md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-10 { 304 | color: #f4adf4; 305 | } 306 | 307 | .md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-3, 308 | .md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-7, 309 | .md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-11 { 310 | color: #6cb8e6; 311 | } 312 | 313 | .md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-4, 314 | .md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-8, 315 | .md-code-block.md-code-block-dark .rainbow-braces .token.token.punctuation.brace-level-12 { 316 | color: #c699e3; 317 | } 318 | 319 | .md-code-block.md-code-block-dark pre.diff-highlight > code .token.token.deleted:not(.prefix), 320 | .md-code-block.md-code-block-dark pre > code.diff-highlight .token.token.deleted:not(.prefix) { 321 | background-color: rgba(205, 102, 96, 0.12); 322 | } 323 | 324 | .md-code-block.md-code-block-dark pre.diff-highlight > code .token.token.inserted:not(.prefix), 325 | .md-code-block.md-code-block-dark pre > code.diff-highlight .token.token.inserted:not(.prefix) { 326 | background-color: rgba(145, 208, 118, 0.12); 327 | } 328 | 329 | .md-code-block.md-code-block-dark .command-line .command-line-prompt { 330 | border-right: 1px solid #0b121b; 331 | } 332 | 333 | .md-code-block.md-code-block-dark .command-line .command-line-prompt > span:before { 334 | color: rgba(141, 161, 185, 0.855); 335 | } 336 | 337 | .md-code-block.md-code-block-light code[class*='language-'], 338 | .md-code-block.md-code-block-light pre[class*='language-'] { 339 | color: #383a42; 340 | text-align: left; 341 | white-space: pre; 342 | word-spacing: normal; 343 | word-break: normal; 344 | -moz-tab-size: 2; 345 | tab-size: 2; 346 | -webkit-hyphens: none; 347 | -ms-hyphens: none; 348 | hyphens: none; 349 | direction: ltr; 350 | background: #fafafa; 351 | font-family: 352 | Fira Code, 353 | Fira Mono, 354 | Menlo, 355 | Consolas, 356 | DejaVu Sans Mono, 357 | monospace; 358 | line-height: 1.5; 359 | } 360 | 361 | .md-code-block.md-code-block-light code[class*='language-']::-moz-selection { 362 | color: inherit; 363 | background: #e5e5e6; 364 | } 365 | 366 | .md-code-block.md-code-block-light code[class*='language-'] ::-moz-selection { 367 | color: inherit; 368 | background: #e5e5e6; 369 | } 370 | 371 | .md-code-block.md-code-block-light pre[class*='language-'] ::-moz-selection { 372 | color: inherit; 373 | background: #e5e5e6; 374 | } 375 | 376 | .md-code-block.md-code-block-light code[class*='language-']::-moz-selection { 377 | color: inherit; 378 | background: #e5e5e6; 379 | } 380 | 381 | .md-code-block.md-code-block-light code[class*='language-'] ::-moz-selection { 382 | color: inherit; 383 | background: #e5e5e6; 384 | } 385 | 386 | .md-code-block.md-code-block-light pre[class*='language-'] ::-moz-selection { 387 | color: inherit; 388 | background: #e5e5e6; 389 | } 390 | 391 | .md-code-block.md-code-block-light code[class*='language-']::-moz-selection { 392 | color: inherit; 393 | background: #e5e5e6; 394 | } 395 | 396 | .md-code-block.md-code-block-light code[class*='language-']::selection { 397 | color: inherit; 398 | background: #e5e5e6; 399 | } 400 | 401 | .md-code-block.md-code-block-light code[class*='language-'] ::-moz-selection { 402 | color: inherit; 403 | background: #e5e5e6; 404 | } 405 | 406 | .md-code-block.md-code-block-light code[class*='language-'] ::selection { 407 | color: inherit; 408 | background: #e5e5e6; 409 | } 410 | 411 | .md-code-block.md-code-block-light pre[class*='language-'] ::-moz-selection { 412 | color: inherit; 413 | background: #e5e5e6; 414 | } 415 | 416 | .md-code-block.md-code-block-light pre[class*='language-'] ::selection { 417 | color: inherit; 418 | background: #e5e5e6; 419 | } 420 | 421 | .md-code-block.md-code-block-light pre[class*='language-'] { 422 | border-radius: 0.3em; 423 | margin: 0.5em 0; 424 | padding: 1em; 425 | overflow: auto; 426 | } 427 | 428 | .md-code-block.md-code-block-light :not(pre) > code[class*='language-'] { 429 | white-space: normal; 430 | border-radius: 0.3em; 431 | padding: 0.2em 0.3em; 432 | } 433 | 434 | .md-code-block.md-code-block-light .token.comment, 435 | .md-code-block.md-code-block-light .token.prolog, 436 | .md-code-block.md-code-block-light .token.cdata { 437 | color: #a0a1a7; 438 | } 439 | 440 | .md-code-block.md-code-block-light .token.doctype, 441 | .md-code-block.md-code-block-light .token.punctuation, 442 | .md-code-block.md-code-block-light .token.entity { 443 | color: #383a42; 444 | } 445 | 446 | .md-code-block.md-code-block-light .token.attr-name, 447 | .md-code-block.md-code-block-light .token.class-name, 448 | .md-code-block.md-code-block-light .token.boolean, 449 | .md-code-block.md-code-block-light .token.constant, 450 | .md-code-block.md-code-block-light .token.number, 451 | .md-code-block.md-code-block-light .token.atrule { 452 | color: #b76b01; 453 | } 454 | 455 | .md-code-block.md-code-block-light .token.keyword { 456 | color: #a626a4; 457 | } 458 | 459 | .md-code-block.md-code-block-light .token.property, 460 | .md-code-block.md-code-block-light .token.tag, 461 | .md-code-block.md-code-block-light .token.symbol, 462 | .md-code-block.md-code-block-light .token.deleted, 463 | .md-code-block.md-code-block-light .token.important { 464 | color: #e45649; 465 | } 466 | 467 | .md-code-block.md-code-block-light .token.selector, 468 | .md-code-block.md-code-block-light .token.string, 469 | .md-code-block.md-code-block-light .token.char, 470 | .md-code-block.md-code-block-light .token.builtin, 471 | .md-code-block.md-code-block-light .token.inserted, 472 | .md-code-block.md-code-block-light .token.regex, 473 | .md-code-block.md-code-block-light .token.attr-value, 474 | .md-code-block.md-code-block-light .token.attr-value > .token.punctuation { 475 | color: #50a14f; 476 | } 477 | 478 | .md-code-block.md-code-block-light .token.variable, 479 | .md-code-block.md-code-block-light .token.operator, 480 | .md-code-block.md-code-block-light .token.function { 481 | color: #4078f2; 482 | } 483 | 484 | .md-code-block.md-code-block-light .token.url { 485 | color: #0184bc; 486 | } 487 | 488 | .md-code-block.md-code-block-light .token.attr-value > .token.punctuation.attr-equals, 489 | .md-code-block.md-code-block-light .token.special-attr > .token.attr-value > .token.value.css { 490 | color: #383a42; 491 | } 492 | 493 | .md-code-block.md-code-block-light .language-css .token.selector { 494 | color: #e45649; 495 | } 496 | 497 | .md-code-block.md-code-block-light .language-css .token.property { 498 | color: #383a42; 499 | } 500 | 501 | .md-code-block.md-code-block-light .language-css .token.function, 502 | .md-code-block.md-code-block-light .language-css .token.url > .token.function { 503 | color: #0184bc; 504 | } 505 | 506 | .md-code-block.md-code-block-light .language-css .token.url > .token.string.url { 507 | color: #50a14f; 508 | } 509 | 510 | .md-code-block.md-code-block-light .language-css .token.important, 511 | .md-code-block.md-code-block-light .language-css .token.atrule .token.rule, 512 | .md-code-block.md-code-block-light .language-javascript .token.operator { 513 | color: #a626a4; 514 | } 515 | 516 | .md-code-block.md-code-block-light .language-javascript .token.template-string > .token.interpolation > .token.interpolation-punctuation.punctuation { 517 | color: #ca1243; 518 | } 519 | 520 | .md-code-block.md-code-block-light .language-json .token.operator { 521 | color: #383a42; 522 | } 523 | 524 | .md-code-block.md-code-block-light .language-json .token.null.keyword { 525 | color: #b76b01; 526 | } 527 | 528 | .md-code-block.md-code-block-light .language-markdown .token.url, 529 | .md-code-block.md-code-block-light .language-markdown .token.url > .token.operator, 530 | .md-code-block.md-code-block-light .language-markdown .token.url-reference.url > .token.string { 531 | color: #383a42; 532 | } 533 | 534 | .md-code-block.md-code-block-light .language-markdown .token.url > .token.content { 535 | color: #4078f2; 536 | } 537 | 538 | .md-code-block.md-code-block-light .language-markdown .token.url > .token.url, 539 | .md-code-block.md-code-block-light .language-markdown .token.url-reference.url { 540 | color: #0184bc; 541 | } 542 | 543 | .md-code-block.md-code-block-light .language-markdown .token.blockquote.punctuation, 544 | .md-code-block.md-code-block-light .language-markdown .token.hr.punctuation { 545 | color: #a0a1a7; 546 | font-style: italic; 547 | } 548 | 549 | .md-code-block.md-code-block-light .language-markdown .token.code-snippet { 550 | color: #50a14f; 551 | } 552 | 553 | .md-code-block.md-code-block-light .language-markdown .token.bold .token.content { 554 | color: #b76b01; 555 | } 556 | 557 | .md-code-block.md-code-block-light .language-markdown .token.italic .token.content { 558 | color: #a626a4; 559 | } 560 | 561 | .md-code-block.md-code-block-light .language-markdown .token.strike .token.content, 562 | .md-code-block.md-code-block-light .language-markdown .token.strike .token.punctuation, 563 | .md-code-block.md-code-block-light .language-markdown .token.list.punctuation, 564 | .md-code-block.md-code-block-light .language-markdown .token.title.important > .token.punctuation { 565 | color: #e45649; 566 | } 567 | 568 | .md-code-block.md-code-block-light .token.bold { 569 | font-weight: 700; 570 | } 571 | 572 | .md-code-block.md-code-block-light .token.comment, 573 | .md-code-block.md-code-block-light .token.italic { 574 | font-style: italic; 575 | } 576 | 577 | .md-code-block.md-code-block-light .token.entity { 578 | cursor: help; 579 | } 580 | 581 | .md-code-block.md-code-block-light .token.namespace { 582 | opacity: 0.8; 583 | } 584 | 585 | .md-code-block.md-code-block-light .token.token.tab:not(:empty):before, 586 | .md-code-block.md-code-block-light .token.token.cr:before, 587 | .md-code-block.md-code-block-light .token.token.lf:before, 588 | .md-code-block.md-code-block-light .token.token.space:before { 589 | color: rgba(56, 58, 66, 0.2); 590 | } 591 | 592 | .md-code-block-light { 593 | --ds-md-code-block-border-color: #e5e5e5; 594 | --ds-md-code-block-banner-wrap-background-color: white; 595 | --ds-md-code-banner-background-color: #f5f5f5; 596 | color: #494949; 597 | background: #fafafa; 598 | } 599 | 600 | .md-code-block-dark { 601 | --ds-md-code-block-border-color: var(--dsr-bg, transparent); 602 | --ds-md-code-block-banner-wrap-background-color: #292a2d; 603 | --ds-md-code-banner-background-color: #404045; 604 | color: #fff; 605 | background: #212327; 606 | } 607 | 608 | .md-code-block { 609 | --ds-md-code-block-border-radius: 12px; 610 | --ds-md-code-block-font-size: calc(var(--ds-md-zoom) * var(--ds-font-size-xsp)); 611 | border-radius: var(--ds-md-code-block-border-radius); 612 | font-size: var(--ds-md-code-block-font-size); 613 | line-height: calc(var(--ds-md-code-block-font-size) * 1.6); 614 | } 615 | 616 | .md-code-block:not(:last-child) { 617 | margin-bottom: calc(var(--ds-md-zoom) * 10px); 618 | } 619 | 620 | .md-code-block-banner-wrap { 621 | background-color: var(--ds-md-code-block-banner-wrap-background-color); 622 | z-index: 6; 623 | position: -webkit-sticky; 624 | position: sticky; 625 | top: 0; 626 | } 627 | 628 | .md-code-block-banner { 629 | background: var(--ds-md-code-banner-background-color); 630 | padding: calc(var(--ds-md-zoom) * 8px) calc(var(--ds-md-zoom) * 12px); 631 | font-size: var(--ds-md-code-block-font-size); 632 | line-height: var(--ds-md-code-block-font-size); 633 | justify-content: space-between; 634 | display: flex; 635 | } 636 | 637 | .md-code-block-banner-lite { 638 | padding: 0; 639 | } 640 | 641 | .md-code-block-banner { 642 | border-top-left-radius: var(--ds-md-code-block-border-radius); 643 | border-top-right-radius: var(--ds-md-code-block-border-radius); 644 | } 645 | 646 | .md-code-block-footer { 647 | background: var(--ds-md-code-banner-background-color); 648 | padding: calc(var(--ds-md-zoom) * 8px) calc(var(--ds-md-zoom) * 12px); 649 | font-size: var(--ds-md-code-block-font-size); 650 | line-height: var(--ds-md-code-block-font-size); 651 | border-bottom-left-radius: var(--ds-md-code-block-border-radius); 652 | border-bottom-right-radius: var(--ds-md-code-block-border-radius); 653 | justify-content: flex-end; 654 | display: flex; 655 | } 656 | 657 | .md-code-block-action { 658 | align-items: center; 659 | display: flex; 660 | } 661 | 662 | .md-code-block > pre { 663 | padding: calc(var(--ds-md-zoom) * 8px) calc(var(--ds-md-zoom) * 12px); 664 | white-space: pre-wrap; 665 | word-break: break-all; 666 | margin: 0 !important; 667 | } 668 | -------------------------------------------------------------------------------- /src/components/HighReactMarkdown/index.less: -------------------------------------------------------------------------------- 1 | .ds-markdown { 2 | .markdown-table-wrapper { 3 | overflow-x: auto; 4 | } 5 | .markdown-table-wrapper table { 6 | width: max-content; 7 | max-width: max-content; 8 | min-width: 100%; 9 | } 10 | 11 | .markdown-table-wrapper th, 12 | .markdown-table-wrapper td { 13 | padding: 10px; 14 | font-size: 15px; 15 | line-height: 1.72; 16 | } 17 | 18 | .markdown-table-wrapper th { 19 | border-color: var(--dsr-border-1); 20 | border-top: none; 21 | } 22 | 23 | .markdown-table-wrapper td { 24 | border-color: var(--dsr-border-2); 25 | max-width: 320px; 26 | min-width: 100px; 27 | max-width: max(30vw, 320px); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/HighReactMarkdown/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { memo } from 'react'; 2 | import Markdown from 'react-markdown'; 3 | import type { Options } from 'react-markdown'; 4 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; 5 | import gfmPlugin from 'remark-gfm'; 6 | 7 | import BlockWrap from '../BlockWrap/index.js'; 8 | 9 | interface HighReactMarkdownProps extends Options { 10 | id?: number; 11 | } 12 | 13 | const modulePrefix = 'HighReactMarkdown'; 14 | const HighReactMarkdown: React.FC = (props) => { 15 | return ( 16 | { 20 | const match = /language-(\w+)/.exec(className || ''); 21 | return match ? ( 22 | 23 | 24 | {String(children).replace(/\n$/, '')} 25 | 26 | 27 | ) : ( 28 | 29 | {children} 30 | 31 | ); 32 | }, 33 | table: ({ children, ...props }) => { 34 | return ( 35 |
36 | {children}
37 |
38 | ); 39 | }, 40 | }} 41 | {...props} 42 | /> 43 | ); 44 | }; 45 | export default memo(HighReactMarkdown); 46 | -------------------------------------------------------------------------------- /src/constant.ts: -------------------------------------------------------------------------------- 1 | export const __DEV__ = process.env.NODE_ENV === 'development'; 2 | -------------------------------------------------------------------------------- /src/defined.less: -------------------------------------------------------------------------------- 1 | :root, 2 | page { 3 | --ds-rgb-black: 0 0 0; 4 | --ds-rgb-white: 255 255 255; 5 | --ds-rgb-slate-50: 248 250 252; 6 | --ds-rgb-slate-100: 241 245 249; 7 | --ds-rgb-slate-150: 233 238 244; 8 | --ds-rgb-slate-200: 226 232 240; 9 | --ds-rgb-slate-250: 214 222 232; 10 | --ds-rgb-slate-300: 203 213 225; 11 | --ds-rgb-slate-350: 175 188 204; 12 | --ds-rgb-slate-400: 148 163 184; 13 | --ds-rgb-slate-450: 124 139 161; 14 | --ds-rgb-slate-500: 100 116 139; 15 | --ds-rgb-slate-550: 85 100 122; 16 | --ds-rgb-slate-600: 71 85 105; 17 | --ds-rgb-slate-650: 61 75 95; 18 | --ds-rgb-slate-700: 51 65 85; 19 | --ds-rgb-slate-750: 40 53 72; 20 | --ds-rgb-slate-800: 30 41 59; 21 | --ds-rgb-slate-850: 22 32 50; 22 | --ds-rgb-slate-900: 15 23 42; 23 | --ds-rgb-slate-950: 2 6 23; 24 | --ds-rgb-gray-50: 249 250 251; 25 | --ds-rgb-gray-100: 243 244 246; 26 | --ds-rgb-gray-150: 236 237 240; 27 | --ds-rgb-gray-200: 229 231 235; 28 | --ds-rgb-gray-250: 219 222 227; 29 | --ds-rgb-gray-300: 209 213 219; 30 | --ds-rgb-gray-350: 182 188 197; 31 | --ds-rgb-gray-400: 156 163 175; 32 | --ds-rgb-gray-450: 131 138 151; 33 | --ds-rgb-gray-500: 107 114 128; 34 | --ds-rgb-gray-550: 91 99 113; 35 | --ds-rgb-gray-600: 75 85 99; 36 | --ds-rgb-gray-650: 65 75 90; 37 | --ds-rgb-gray-700: 55 65 81; 38 | --ds-rgb-gray-750: 43 53 68; 39 | --ds-rgb-gray-800: 31 41 55; 40 | --ds-rgb-gray-850: 24 32 47; 41 | --ds-rgb-gray-900: 17 24 39; 42 | --ds-rgb-gray-950: 3 7 18; 43 | --ds-rgb-zinc-50: 250 250 250; 44 | --ds-rgb-zinc-100: 244 244 245; 45 | --ds-rgb-zinc-150: 236 236 238; 46 | --ds-rgb-zinc-200: 228 228 231; 47 | --ds-rgb-zinc-250: 220 220 223; 48 | --ds-rgb-zinc-300: 212 212 216; 49 | --ds-rgb-zinc-350: 186 186 193; 50 | --ds-rgb-zinc-400: 161 161 170; 51 | --ds-rgb-zinc-450: 137 137 146; 52 | --ds-rgb-zinc-500: 113 113 122; 53 | --ds-rgb-zinc-550: 97 97 106; 54 | --ds-rgb-zinc-600: 82 82 91; 55 | --ds-rgb-zinc-650: 72 72 80; 56 | --ds-rgb-zinc-700: 63 63 70; 57 | --ds-rgb-zinc-750: 51 51 56; 58 | --ds-rgb-zinc-800: 39 39 42; 59 | --ds-rgb-zinc-850: 31 31 34; 60 | --ds-rgb-zinc-900: 24 24 27; 61 | --ds-rgb-zinc-950: 9 9 11; 62 | --ds-rgb-neutral-50: 250 250 250; 63 | --ds-rgb-neutral-100: 245 245 245; 64 | --ds-rgb-neutral-150: 237 237 237; 65 | --ds-rgb-neutral-200: 229 229 229; 66 | --ds-rgb-neutral-250: 220 220 220; 67 | --ds-rgb-neutral-300: 212 212 212; 68 | --ds-rgb-neutral-350: 187 187 187; 69 | --ds-rgb-neutral-400: 163 163 163; 70 | --ds-rgb-neutral-450: 139 139 139; 71 | --ds-rgb-neutral-500: 115 115 115; 72 | --ds-rgb-neutral-550: 98 98 98; 73 | --ds-rgb-neutral-600: 82 82 82; 74 | --ds-rgb-neutral-650: 73 73 73; 75 | --ds-rgb-neutral-700: 64 64 64; 76 | --ds-rgb-neutral-750: 51 51 51; 77 | --ds-rgb-neutral-800: 38 38 38; 78 | --ds-rgb-neutral-850: 30 30 30; 79 | --ds-rgb-neutral-900: 23 23 23; 80 | --ds-rgb-neutral-950: 10 10 10; 81 | --ds-rgb-stone-50: 250 250 249; 82 | --ds-rgb-stone-100: 245 245 244; 83 | --ds-rgb-stone-150: 238 237 236; 84 | --ds-rgb-stone-200: 231 229 228; 85 | --ds-rgb-stone-250: 222 220 218; 86 | --ds-rgb-stone-300: 214 211 209; 87 | --ds-rgb-stone-350: 191 186 183; 88 | --ds-rgb-stone-400: 168 162 158; 89 | --ds-rgb-stone-450: 144 137 133; 90 | --ds-rgb-stone-500: 120 113 108; 91 | --ds-rgb-stone-550: 103 98 93; 92 | --ds-rgb-stone-600: 87 83 78; 93 | --ds-rgb-stone-650: 77 73 69; 94 | --ds-rgb-stone-700: 68 64 60; 95 | --ds-rgb-stone-750: 54 50 48; 96 | --ds-rgb-stone-800: 41 37 36; 97 | --ds-rgb-stone-850: 34 31 29; 98 | --ds-rgb-stone-900: 28 25 23; 99 | --ds-rgb-stone-950: 12 10 9; 100 | --ds-rgb-red-50: 254 242 242; 101 | --ds-rgb-red-100: 254 226 226; 102 | --ds-rgb-red-150: 254 214 214; 103 | --ds-rgb-red-200: 254 202 202; 104 | --ds-rgb-red-250: 253 183 183; 105 | --ds-rgb-red-300: 252 165 165; 106 | --ds-rgb-red-350: 250 139 139; 107 | --ds-rgb-red-400: 248 113 113; 108 | --ds-rgb-red-450: 243 90 90; 109 | --ds-rgb-red-500: 239 68 68; 110 | --ds-rgb-red-550: 229 53 53; 111 | --ds-rgb-red-600: 220 38 38; 112 | --ds-rgb-red-650: 202 33 33; 113 | --ds-rgb-red-700: 185 28 28; 114 | --ds-rgb-red-750: 169 27 27; 115 | --ds-rgb-red-800: 153 27 27; 116 | --ds-rgb-red-850: 140 28 28; 117 | --ds-rgb-red-900: 127 29 29; 118 | --ds-rgb-red-950: 69 10 10; 119 | --ds-rgb-orange-50: 255 247 237; 120 | --ds-rgb-orange-100: 255 237 213; 121 | --ds-rgb-orange-150: 254 226 191; 122 | --ds-rgb-orange-200: 254 215 170; 123 | --ds-rgb-orange-250: 253 200 143; 124 | --ds-rgb-orange-300: 253 186 116; 125 | --ds-rgb-orange-350: 252 166 88; 126 | --ds-rgb-orange-400: 251 146 60; 127 | --ds-rgb-orange-450: 250 130 41; 128 | --ds-rgb-orange-500: 249 115 22; 129 | --ds-rgb-orange-550: 241 101 17; 130 | --ds-rgb-orange-600: 234 88 12; 131 | --ds-rgb-orange-650: 214 76 12; 132 | --ds-rgb-orange-700: 194 65 12; 133 | --ds-rgb-orange-750: 174 58 15; 134 | --ds-rgb-orange-800: 154 52 18; 135 | --ds-rgb-orange-850: 139 48 18; 136 | --ds-rgb-orange-900: 124 45 18; 137 | --ds-rgb-orange-950: 67 20 7; 138 | --ds-rgb-amber-50: 255 251 235; 139 | --ds-rgb-amber-100: 254 243 199; 140 | --ds-rgb-amber-150: 253 236 168; 141 | --ds-rgb-amber-200: 253 230 138; 142 | --ds-rgb-amber-250: 252 220 107; 143 | --ds-rgb-amber-300: 252 211 77; 144 | --ds-rgb-amber-350: 251 201 56; 145 | --ds-rgb-amber-400: 251 191 36; 146 | --ds-rgb-amber-450: 248 174 23; 147 | --ds-rgb-amber-500: 245 158 11; 148 | --ds-rgb-amber-550: 231 138 8; 149 | --ds-rgb-amber-600: 217 119 6; 150 | --ds-rgb-amber-650: 198 101 7; 151 | --ds-rgb-amber-700: 180 83 9; 152 | --ds-rgb-amber-750: 163 73 11; 153 | --ds-rgb-amber-800: 146 64 14; 154 | --ds-rgb-amber-850: 133 58 14; 155 | --ds-rgb-amber-900: 120 53 15; 156 | --ds-rgb-amber-950: 69 26 3; 157 | --ds-rgb-yellow-50: 254 252 232; 158 | --ds-rgb-yellow-100: 254 249 195; 159 | --ds-rgb-yellow-150: 254 244 166; 160 | --ds-rgb-yellow-200: 254 240 138; 161 | --ds-rgb-yellow-250: 253 232 104; 162 | --ds-rgb-yellow-300: 253 224 71; 163 | --ds-rgb-yellow-350: 251 214 46; 164 | --ds-rgb-yellow-400: 250 204 21; 165 | --ds-rgb-yellow-450: 242 191 14; 166 | --ds-rgb-yellow-500: 234 179 8; 167 | --ds-rgb-yellow-550: 218 158 6; 168 | --ds-rgb-yellow-600: 202 138 4; 169 | --ds-rgb-yellow-650: 181 118 5; 170 | --ds-rgb-yellow-700: 161 98 7; 171 | --ds-rgb-yellow-750: 147 87 10; 172 | --ds-rgb-yellow-800: 133 77 14; 173 | --ds-rgb-yellow-850: 123 70 16; 174 | --ds-rgb-yellow-900: 113 63 18; 175 | --ds-rgb-yellow-950: 66 32 6; 176 | --ds-rgb-lime-50: 247 254 231; 177 | --ds-rgb-lime-100: 236 252 203; 178 | --ds-rgb-lime-150: 226 250 180; 179 | --ds-rgb-lime-200: 217 249 157; 180 | --ds-rgb-lime-250: 203 245 128; 181 | --ds-rgb-lime-300: 190 242 100; 182 | --ds-rgb-lime-350: 176 236 76; 183 | --ds-rgb-lime-400: 163 230 53; 184 | --ds-rgb-lime-450: 147 217 37; 185 | --ds-rgb-lime-500: 132 204 22; 186 | --ds-rgb-lime-550: 116 183 17; 187 | --ds-rgb-lime-600: 101 163 13; 188 | --ds-rgb-lime-650: 89 143 14; 189 | --ds-rgb-lime-700: 77 124 15; 190 | --ds-rgb-lime-750: 70 111 16; 191 | --ds-rgb-lime-800: 63 98 18; 192 | --ds-rgb-lime-850: 58 90 19; 193 | --ds-rgb-lime-900: 54 83 20; 194 | --ds-rgb-lime-950: 26 46 5; 195 | --ds-rgb-green-50: 240 253 244; 196 | --ds-rgb-green-100: 220 252 231; 197 | --ds-rgb-green-150: 203 249 219; 198 | --ds-rgb-green-200: 187 247 208; 199 | --ds-rgb-green-250: 160 243 190; 200 | --ds-rgb-green-300: 134 239 172; 201 | --ds-rgb-green-350: 104 230 150; 202 | --ds-rgb-green-400: 74 222 128; 203 | --ds-rgb-green-450: 54 209 111; 204 | --ds-rgb-green-500: 34 197 94; 205 | --ds-rgb-green-550: 28 180 84; 206 | --ds-rgb-green-600: 22 163 74; 207 | --ds-rgb-green-650: 21 145 67; 208 | --ds-rgb-green-700: 21 128 61; 209 | --ds-rgb-green-750: 21 114 56; 210 | --ds-rgb-green-800: 22 101 52; 211 | --ds-rgb-green-850: 21 92 48; 212 | --ds-rgb-green-900: 20 83 45; 213 | --ds-rgb-green-950: 5 46 22; 214 | --ds-rgb-emerald-50: 236 253 245; 215 | --ds-rgb-emerald-100: 209 250 229; 216 | --ds-rgb-emerald-150: 188 246 218; 217 | --ds-rgb-emerald-200: 167 243 208; 218 | --ds-rgb-emerald-250: 138 237 195; 219 | --ds-rgb-emerald-300: 110 231 183; 220 | --ds-rgb-emerald-350: 81 221 168; 221 | --ds-rgb-emerald-400: 52 211 153; 222 | --ds-rgb-emerald-450: 34 198 141; 223 | --ds-rgb-emerald-500: 16 185 129; 224 | --ds-rgb-emerald-550: 10 167 117; 225 | --ds-rgb-emerald-600: 5 150 105; 226 | --ds-rgb-emerald-650: 4 135 96; 227 | --ds-rgb-emerald-700: 4 120 87; 228 | --ds-rgb-emerald-750: 5 107 78; 229 | --ds-rgb-emerald-800: 6 95 70; 230 | --ds-rgb-emerald-850: 6 86 64; 231 | --ds-rgb-emerald-900: 6 78 59; 232 | --ds-rgb-emerald-950: 2 44 34; 233 | --ds-rgb-teal-50: 240 253 250; 234 | --ds-rgb-teal-100: 204 251 241; 235 | --ds-rgb-teal-150: 178 248 234; 236 | --ds-rgb-teal-200: 153 246 228; 237 | --ds-rgb-teal-250: 123 240 220; 238 | --ds-rgb-teal-300: 94 234 212; 239 | --ds-rgb-teal-350: 69 223 201; 240 | --ds-rgb-teal-400: 45 212 191; 241 | --ds-rgb-teal-450: 32 198 178; 242 | --ds-rgb-teal-500: 20 184 166; 243 | --ds-rgb-teal-550: 16 166 151; 244 | --ds-rgb-teal-600: 13 148 136; 245 | --ds-rgb-teal-650: 14 133 123; 246 | --ds-rgb-teal-700: 15 118 110; 247 | --ds-rgb-teal-750: 16 106 99; 248 | --ds-rgb-teal-800: 17 94 89; 249 | --ds-rgb-teal-850: 18 86 81; 250 | --ds-rgb-teal-900: 19 78 74; 251 | --ds-rgb-teal-950: 4 47 46; 252 | --ds-rgb-cyan-50: 236 254 255; 253 | --ds-rgb-cyan-100: 207 250 254; 254 | --ds-rgb-cyan-150: 186 246 253; 255 | --ds-rgb-cyan-200: 165 243 252; 256 | --ds-rgb-cyan-250: 134 237 250; 257 | --ds-rgb-cyan-300: 103 232 249; 258 | --ds-rgb-cyan-350: 68 221 243; 259 | --ds-rgb-cyan-400: 34 211 238; 260 | --ds-rgb-cyan-450: 20 196 225; 261 | --ds-rgb-cyan-500: 6 182 212; 262 | --ds-rgb-cyan-550: 7 163 195; 263 | --ds-rgb-cyan-600: 8 145 178; 264 | --ds-rgb-cyan-650: 11 130 161; 265 | --ds-rgb-cyan-700: 14 116 144; 266 | --ds-rgb-cyan-750: 17 105 130; 267 | --ds-rgb-cyan-800: 21 94 117; 268 | --ds-rgb-cyan-850: 21 86 108; 269 | --ds-rgb-cyan-900: 22 78 99; 270 | --ds-rgb-cyan-950: 8 51 68; 271 | --ds-rgb-sky-50: 240 249 255; 272 | --ds-rgb-sky-100: 224 242 254; 273 | --ds-rgb-sky-150: 205 236 253; 274 | --ds-rgb-sky-200: 186 230 253; 275 | --ds-rgb-sky-250: 155 220 252; 276 | --ds-rgb-sky-300: 125 211 252; 277 | --ds-rgb-sky-350: 90 200 250; 278 | --ds-rgb-sky-400: 56 189 248; 279 | --ds-rgb-sky-450: 35 177 240; 280 | --ds-rgb-sky-500: 14 165 233; 281 | --ds-rgb-sky-550: 8 148 216; 282 | --ds-rgb-sky-600: 2 132 199; 283 | --ds-rgb-sky-650: 2 118 180; 284 | --ds-rgb-sky-700: 3 105 161; 285 | --ds-rgb-sky-750: 5 97 147; 286 | --ds-rgb-sky-800: 7 89 133; 287 | --ds-rgb-sky-850: 9 81 121; 288 | --ds-rgb-sky-900: 12 74 110; 289 | --ds-rgb-sky-950: 8 47 73; 290 | --ds-rgb-blue-50: 239 246 255; 291 | --ds-rgb-blue-100: 219 234 254; 292 | --ds-rgb-blue-150: 205 226 254; 293 | --ds-rgb-blue-200: 191 219 254; 294 | --ds-rgb-blue-250: 169 208 253; 295 | --ds-rgb-blue-300: 147 197 253; 296 | --ds-rgb-blue-350: 121 181 251; 297 | --ds-rgb-blue-400: 96 165 250; 298 | --ds-rgb-blue-450: 77 147 248; 299 | --ds-rgb-blue-500: 59 130 246; 300 | --ds-rgb-blue-550: 48 114 240; 301 | --ds-rgb-blue-600: 37 99 235; 302 | --ds-rgb-blue-650: 33 88 225; 303 | --ds-rgb-blue-700: 29 78 216; 304 | --ds-rgb-blue-750: 29 71 195; 305 | --ds-rgb-blue-800: 30 64 175; 306 | --ds-rgb-blue-850: 30 61 156; 307 | --ds-rgb-blue-900: 30 58 138; 308 | --ds-rgb-blue-950: 23 37 84; 309 | --ds-rgb-indigo-50: 238 242 255; 310 | --ds-rgb-indigo-100: 224 231 255; 311 | --ds-rgb-indigo-150: 211 220 254; 312 | --ds-rgb-indigo-200: 199 210 254; 313 | --ds-rgb-indigo-250: 182 195 253; 314 | --ds-rgb-indigo-300: 165 180 252; 315 | --ds-rgb-indigo-350: 147 160 250; 316 | --ds-rgb-indigo-400: 129 140 248; 317 | --ds-rgb-indigo-450: 114 121 244; 318 | --ds-rgb-indigo-500: 99 102 241; 319 | --ds-rgb-indigo-550: 89 86 235; 320 | --ds-rgb-indigo-600: 79 70 229; 321 | --ds-rgb-indigo-650: 73 63 215; 322 | --ds-rgb-indigo-700: 67 56 202; 323 | --ds-rgb-indigo-750: 61 52 182; 324 | --ds-rgb-indigo-800: 55 48 163; 325 | --ds-rgb-indigo-850: 52 47 146; 326 | --ds-rgb-indigo-900: 49 46 129; 327 | --ds-rgb-indigo-950: 30 27 75; 328 | --ds-rgb-violet-50: 245 243 255; 329 | --ds-rgb-violet-100: 237 233 254; 330 | --ds-rgb-violet-150: 229 223 254; 331 | --ds-rgb-violet-200: 221 214 254; 332 | --ds-rgb-violet-250: 208 197 253; 333 | --ds-rgb-violet-300: 196 181 253; 334 | --ds-rgb-violet-350: 181 160 251; 335 | --ds-rgb-violet-400: 167 139 250; 336 | --ds-rgb-violet-450: 153 115 248; 337 | --ds-rgb-violet-500: 139 92 246; 338 | --ds-rgb-violet-550: 131 75 241; 339 | --ds-rgb-violet-600: 124 58 237; 340 | --ds-rgb-violet-650: 116 49 227; 341 | --ds-rgb-violet-700: 109 40 217; 342 | --ds-rgb-violet-750: 100 36 199; 343 | --ds-rgb-violet-800: 91 33 182; 344 | --ds-rgb-violet-850: 83 31 165; 345 | --ds-rgb-violet-900: 76 29 149; 346 | --ds-rgb-violet-950: 46 16 101; 347 | --ds-rgb-purple-50: 250 245 255; 348 | --ds-rgb-purple-100: 243 232 255; 349 | --ds-rgb-purple-150: 238 222 255; 350 | --ds-rgb-purple-200: 233 213 255; 351 | --ds-rgb-purple-250: 224 196 254; 352 | --ds-rgb-purple-300: 216 180 254; 353 | --ds-rgb-purple-350: 204 156 253; 354 | --ds-rgb-purple-400: 192 132 252; 355 | --ds-rgb-purple-450: 180 108 249; 356 | --ds-rgb-purple-500: 168 85 247; 357 | --ds-rgb-purple-550: 157 68 240; 358 | --ds-rgb-purple-600: 147 51 234; 359 | --ds-rgb-purple-650: 136 42 220; 360 | --ds-rgb-purple-700: 126 34 206; 361 | --ds-rgb-purple-750: 116 33 187; 362 | --ds-rgb-purple-800: 107 33 168; 363 | --ds-rgb-purple-850: 97 30 151; 364 | --ds-rgb-purple-900: 88 28 135; 365 | --ds-rgb-purple-950: 59 7 100; 366 | --ds-rgb-fuchsia-50: 253 244 255; 367 | --ds-rgb-fuchsia-100: 250 232 255; 368 | --ds-rgb-fuchsia-150: 247 220 254; 369 | --ds-rgb-fuchsia-200: 245 208 254; 370 | --ds-rgb-fuchsia-250: 242 189 253; 371 | --ds-rgb-fuchsia-300: 240 171 252; 372 | --ds-rgb-fuchsia-350: 236 146 250; 373 | --ds-rgb-fuchsia-400: 232 121 249; 374 | --ds-rgb-fuchsia-450: 224 95 244; 375 | --ds-rgb-fuchsia-500: 217 70 239; 376 | --ds-rgb-fuchsia-550: 204 54 225; 377 | --ds-rgb-fuchsia-600: 192 38 211; 378 | --ds-rgb-fuchsia-650: 177 33 193; 379 | --ds-rgb-fuchsia-700: 162 28 175; 380 | --ds-rgb-fuchsia-750: 148 26 159; 381 | --ds-rgb-fuchsia-800: 134 25 143; 382 | --ds-rgb-fuchsia-850: 123 25 130; 383 | --ds-rgb-fuchsia-900: 112 26 117; 384 | --ds-rgb-fuchsia-950: 74 4 78; 385 | --ds-rgb-pink-50: 253 242 248; 386 | --ds-rgb-pink-100: 252 231 243; 387 | --ds-rgb-pink-150: 251 219 237; 388 | --ds-rgb-pink-200: 251 207 232; 389 | --ds-rgb-pink-250: 250 187 222; 390 | --ds-rgb-pink-300: 249 168 212; 391 | --ds-rgb-pink-350: 246 141 197; 392 | --ds-rgb-pink-400: 244 114 182; 393 | --ds-rgb-pink-450: 240 93 167; 394 | --ds-rgb-pink-500: 236 72 153; 395 | --ds-rgb-pink-550: 227 55 136; 396 | --ds-rgb-pink-600: 219 39 119; 397 | --ds-rgb-pink-650: 204 31 106; 398 | --ds-rgb-pink-700: 190 24 93; 399 | --ds-rgb-pink-750: 173 23 85; 400 | --ds-rgb-pink-800: 157 23 77; 401 | --ds-rgb-pink-850: 144 23 72; 402 | --ds-rgb-pink-900: 131 24 67; 403 | --ds-rgb-pink-950: 80 7 36; 404 | --ds-rgb-rose-50: 255 241 242; 405 | --ds-rgb-rose-100: 255 228 230; 406 | --ds-rgb-rose-150: 254 216 220; 407 | --ds-rgb-rose-200: 254 205 211; 408 | --ds-rgb-rose-250: 253 184 193; 409 | --ds-rgb-rose-300: 253 164 175; 410 | --ds-rgb-rose-350: 252 138 154; 411 | --ds-rgb-rose-400: 251 113 133; 412 | --ds-rgb-rose-450: 247 88 113; 413 | --ds-rgb-rose-500: 244 63 94; 414 | --ds-rgb-rose-550: 234 46 83; 415 | --ds-rgb-rose-600: 225 29 72; 416 | --ds-rgb-rose-650: 207 23 66; 417 | --ds-rgb-rose-700: 190 18 60; 418 | --ds-rgb-rose-750: 174 18 58; 419 | --ds-rgb-rose-800: 159 18 57; 420 | --ds-rgb-rose-850: 147 18 56; 421 | --ds-rgb-rose-900: 136 19 55; 422 | --ds-rgb-rose-950: 76 5 25; 423 | } 424 | 425 | body, 426 | page, 427 | .ds-theme { 428 | --ds-input-height-l: 44px; 429 | --ds-input-height-m: 34px; 430 | --ds-input-height-s: 30px; 431 | --ds-input-height-xs: 26px; 432 | --ds-font-weight-strong: 600; 433 | --ds-font-size-l: 16px; 434 | --ds-line-height-l: 28px; 435 | --ds-font-size-m: 14px; 436 | --ds-line-height-m: 25px; 437 | --ds-font-size-sp: 13px; 438 | --ds-line-height-sp: 23px; 439 | --ds-font-size-s: 12px; 440 | --ds-line-height-s: 21px; 441 | --ds-font-size-xsp: 11px; 442 | --ds-line-height-xsp: 19.5px; 443 | --ds-font-size-xs: 10px; 444 | --ds-line-height-xs: 18px; 445 | --ds-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); 446 | --ds-ease-in: cubic-bezier(0.4, 0, 1, 1); 447 | --ds-ease-out: cubic-bezier(0, 0, 0.2, 1); 448 | --ds-font-family-code: 449 | Menlo, "Roboto Mono", "Courier New", Courier, monospace, "Inter", sans-serif; 450 | --ds-transition-duration: 0.2s; 451 | --ds-transition-duration-fast: 0.1s; 452 | --ds-transition-duration-slow: 0.3s; 453 | } 454 | 455 | body, 456 | page { 457 | --ds-rgb-info: var(--ds-rgb-blue-500); 458 | --ds-rgb-primary: var(--ds-rgb-blue-500); 459 | --ds-rgb-primary-foreground: var(--ds-rgb-white); 460 | --ds-rgb-label-1: var(--ds-rgb-neutral-700); 461 | --ds-rgb-label-2: var(--ds-rgb-neutral-450); 462 | --ds-rgb-label-3: var(--ds-rgb-neutral-400); 463 | --ds-rgb-error: var(--ds-rgb-red-500); 464 | --ds-rgb-warning: var(--ds-rgb-amber-500); 465 | --ds-rgb-success: var(--ds-rgb-green-500); 466 | --ds-rgba-transparent: 255 255 255/0; 467 | --ds-rgb-bg-base: var(--ds-rgb-white); 468 | --ds-rgb-elevated: var(--ds-rgb-white); 469 | --ds-rgb-track: var(--ds-rgb-neutral-200); 470 | --ds-rgb-hover: var(--ds-rgb-neutral-100); 471 | --ds-rgb-header: var(--ds-rgb-neutral-50); 472 | --ds-rgb-separator: var(--ds-rgb-neutral-100); 473 | --ds-rgb-separator-strong: var(--ds-rgb-neutral-200); 474 | --ds-rgb-input: var(--ds-rgb-neutral-100); 475 | --ds-rgb-input-strong: var(--ds-rgb-neutral-150); 476 | --ds-rgb-input-focus: var(--ds-rgb-white); 477 | --ds-rgb-link: var(--ds-rgb-primary); 478 | --ds-rgb-tag: var(--ds-rgb-neutral-200); 479 | --ds-rgb-segmented: var(--ds-rgb-neutral-100); 480 | --ds-rgb-segmented-button: var(--ds-rgb-white); 481 | --ds-rgb-segmented-separator: var(--ds-rgb-neutral-300); 482 | --ds-rgb-thumb: var(--ds-rgb-white); 483 | font-family: 484 | Inter, 485 | system-ui, 486 | -apple-system, 487 | BlinkMacSystemFont, 488 | Segoe UI, 489 | Roboto, 490 | Noto Sans, 491 | Ubuntu, 492 | Cantarell, 493 | Helvetica Neue, 494 | Oxygen, 495 | Open Sans, 496 | sans-serif; 497 | } 498 | -------------------------------------------------------------------------------- /src/defined.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /** 3 | * 回答类型,思考和回答 4 | */ 5 | export type AnswerType = 'answer' | 'thinking'; 6 | 7 | /** 8 | * 段落类型 9 | * 段落类型为br时,表示换行 10 | * 段落类型为text时,表示文本 11 | */ 12 | export interface IParagraph { 13 | /** 段落内容 */ 14 | content?: string; 15 | /** 是否已打字 */ 16 | isTyped: boolean; 17 | /** 段落类型 */ 18 | type: 'br' | 'text'; 19 | /** 回答类型 */ 20 | answerType: AnswerType; 21 | } 22 | 23 | export interface MarkdownProps { 24 | /** 打字机效果间隔时间 */ 25 | interval: number; 26 | /** 是否关闭匀速打字机效果 */ 27 | isClosePrettyTyped?: boolean; 28 | /** 打字完成后回调, */ 29 | onEnd?: (data?: { str?: string; answerType?: AnswerType }) => void; 30 | /** 开始打字回调 */ 31 | onStart?: (data?: { currentIndex: number; currentChar: string; answerType: AnswerType; prevStr: string }) => void; 32 | /** 33 | * 打字机打完一个字符回调 34 | * @param char 字符 35 | * @param index 字符索引 36 | */ 37 | onTypedChar?: (data?: { currentIndex: number; currentChar: string; answerType: AnswerType; prevStr: string }) => void; 38 | } 39 | -------------------------------------------------------------------------------- /src/index.less: -------------------------------------------------------------------------------- 1 | :root { 2 | --ds-rgb-neutral-50: 250 250 250; 3 | --ds-rgb-neutral-100: 245 245 245; 4 | --ds-rgb-neutral-150: 237 237 237; 5 | --ds-rgb-neutral-200: 229 229 229; 6 | --ds-rgb-neutral-250: 220 220 220; 7 | --ds-rgb-neutral-300: 212 212 212; 8 | --ds-rgb-neutral-350: 187 187 187; 9 | --ds-rgb-neutral-400: 163 163 163; 10 | --ds-rgb-neutral-450: 139 139 139; 11 | --ds-rgb-neutral-500: 115 115 115; 12 | --ds-rgb-neutral-550: 98 98 98; 13 | --ds-rgb-neutral-600: 82 82 82; 14 | --ds-rgb-neutral-650: 73 73 73; 15 | --ds-rgb-neutral-700: 64 64 64; 16 | --ds-rgb-neutral-750: 51 51 51; 17 | --ds-rgb-neutral-800: 38 38 38; 18 | --ds-rgb-neutral-850: 30 30 30; 19 | --ds-rgb-neutral-900: 23 23 23; 20 | --ds-rgb-neutral-950: 10 10 10; 21 | 22 | --ds-rgb-blue-500: 59 130 246; 23 | 24 | --ds-rgb-label-1: var(--ds-rgb-neutral-700); 25 | } 26 | 27 | .ds-markdown { 28 | --ds-font-family-code: Menlo, 'Roboto Mono', 'Courier New', Courier, monospace, 'Inter', sans-serif; 29 | font-family: 30 | Inter, 31 | system-ui, 32 | -apple-system, 33 | BlinkMacSystemFont, 34 | Segoe UI, 35 | Roboto, 36 | Noto Sans, 37 | Ubuntu, 38 | Cantarell, 39 | Helvetica Neue, 40 | Oxygen, 41 | Open Sans, 42 | sans-serif; 43 | } 44 | 45 | .ds-markdown { 46 | --ds-rgb-primary: var(--ds-rgb-blue-500); 47 | 48 | --ds-rgb-label-1: var(--ds-rgb-neutral-700); 49 | --ds-rgb-label-2: var(--ds-rgb-neutral-450); 50 | --ds-rgb-label-3: var(--ds-rgb-neutral-400); 51 | --dsr-border-1: rgb(var(--ds-rgb-neutral-350)); 52 | --dsr-border-2: rgb(var(--ds-rgb-neutral-200)); 53 | } 54 | 55 | .ds-markdown { 56 | --ds-font-size-m: 14px; 57 | --ds-font-size-xsp: 11px; 58 | --ds-line-height-m: 25px; 59 | --ds-transition-duration: 0.2s; 60 | --ds-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); 61 | --ds-rgba-transparent: 255 255 255 / 0; 62 | --ds-rgb-link: var(--ds-rgb-primary); 63 | } 64 | 65 | .ds-markdown.apple { 66 | --ds-font-weight-strong: 500; 67 | } 68 | 69 | .ds-markdown { 70 | --ds-md-zoom: 1.143; 71 | --ds-md-font-size: calc(var(--ds-md-zoom) * var(--ds-font-size-m)); 72 | --ds-md-line-height: calc(var(--ds-md-zoom) * var(--ds-line-height-m)); 73 | font-size: var(--ds-md-font-size); 74 | min-height: var(--ds-md-font-size); 75 | line-height: var(--ds-md-line-height); 76 | color: rgb(var(--ds-rgb-label-1)); 77 | } 78 | 79 | .ds-markdown img { 80 | max-width: 100%; 81 | } 82 | 83 | .ds-markdown h1, 84 | .ds-markdown h2, 85 | .ds-markdown h3, 86 | .ds-markdown h4, 87 | .ds-markdown h5, 88 | .ds-markdown h6 { 89 | font-weight: var(--ds-font-weight-strong); 90 | font-size: var(--ds-md-font-size); 91 | line-height: var(--ds-md-line-height); 92 | margin: calc(var(--ds-md-zoom) * 16px) 0 calc(var(--ds-md-zoom) * 12px) 0; 93 | } 94 | 95 | .ds-markdown h1, 96 | .ds-markdown h2, 97 | .ds-markdown h3, 98 | .ds-markdown h4, 99 | .ds-markdown h5, 100 | .ds-markdown h6 { 101 | font-weight: var(--ds-font-weight-strong); 102 | font-size: var(--ds-md-font-size); 103 | line-height: var(--ds-md-line-height); 104 | margin: calc(var(--ds-md-zoom) * 16px) 0 calc(var(--ds-md-zoom) * 12px) 0; 105 | } 106 | 107 | .ds-markdown h1 { 108 | font-size: calc(var(--ds-md-zoom) * 24px); 109 | line-height: 1.5; 110 | } 111 | 112 | .ds-markdown h2 { 113 | font-size: calc(var(--ds-md-zoom) * 20px); 114 | line-height: 1.5; 115 | } 116 | 117 | .ds-markdown h3 { 118 | font-size: calc(var(--ds-md-zoom) * 16px); 119 | line-height: 1.5; 120 | } 121 | 122 | .ds-markdown h1 .header-anchor, 123 | .ds-markdown h2 .header-anchor, 124 | .ds-markdown h3 .header-anchor, 125 | .ds-markdown h4 .header-anchor, 126 | .ds-markdown h5 .header-anchor, 127 | .ds-markdown h6 .header-anchor { 128 | opacity: 0; 129 | margin-left: 4px; 130 | } 131 | 132 | .ds-markdown h1:hover .header-anchor, 133 | .ds-markdown h2:hover .header-anchor, 134 | .ds-markdown h3:hover .header-anchor, 135 | .ds-markdown h4:hover .header-anchor, 136 | .ds-markdown h5:hover .header-anchor, 137 | .ds-markdown h6:hover .header-anchor { 138 | opacity: 1; 139 | } 140 | 141 | .ds-markdown .ds-markdown-paragraph { 142 | margin: calc(var(--ds-md-zoom) * 12px) 0; 143 | font-size: var(--ds-md-font-size); 144 | line-height: var(--ds-md-line-height); 145 | } 146 | .ds-markdown a:not(.ds-a) { 147 | color: rgb(var(--ds-rgb-link)); 148 | transition: box-shadow var(--ds-transition-duration) var(--ds-ease-in-out); 149 | border-radius: calc(var(--ds-md-zoom) * 6px); 150 | border-left: 3px solid rgba(var(--ds-rgba-transparent)); 151 | border-right: 3px solid rgba(var(--ds-rgba-transparent)); 152 | border-top: 2px solid rgba(var(--ds-rgba-transparent)); 153 | border-bottom: 2px solid rgba(var(--ds-rgba-transparent)); 154 | margin-left: -3px; 155 | margin-right: -3px; 156 | text-decoration: none; 157 | position: relative; 158 | } 159 | 160 | .ds-markdown a:not(.ds-a):focus { 161 | outline: none; 162 | } 163 | 164 | .ds-markdown a:not(.ds-a):focus-visible { 165 | box-shadow: 0 0 0 2px rgb(var(--ds-rgb-primary)); 166 | } 167 | 168 | .ds-markdown li > ul, 169 | .ds-markdown li > ol { 170 | margin-top: 4px; 171 | } 172 | 173 | .ds-markdown ul, 174 | .ds-markdown ol { 175 | margin: calc(var(--ds-md-zoom) * 12px) 0; 176 | padding-left: calc(var(--ds-md-zoom) * 24px); 177 | } 178 | 179 | .ds-markdown li:not(:first-child) { 180 | margin-top: 4px; 181 | } 182 | 183 | .ds-markdown li::marker { 184 | line-height: var(--ds-md-line-height); 185 | color: rgb(var(--ds-rgb-label-2)); 186 | } 187 | 188 | .ds-markdown hr { 189 | height: 1px; 190 | margin: 32px 0; 191 | background-color: var(--dsr-border-2); 192 | border: none; 193 | display: block; 194 | } 195 | 196 | .ds-markdown blockquote { 197 | border-left: 2px solid rgb(var(--ds-rgb-label-3)); 198 | padding-left: calc(var(--ds-md-zoom) * 16px); 199 | margin: 0; 200 | } 201 | 202 | .ds-markdown table { 203 | border-collapse: collapse; 204 | } 205 | 206 | .ds-markdown th { 207 | color: rgb(var(--ds-rgb-label-1)); 208 | padding: calc(var(--ds-md-zoom) * 6px) calc(var(--ds-md-zoom) * 12px); 209 | border-bottom: 1px solid rgb(var(--ds-rgb-label-3)); 210 | border-top: 1px solid rgb(var(--ds-rgb-label-3)); 211 | font-weight: 600; 212 | } 213 | 214 | .ds-markdown th, 215 | .ds-markdown td { 216 | text-align: left; 217 | } 218 | 219 | .ds-markdown th:first-child { 220 | padding-left: 0; 221 | } 222 | 223 | .ds-markdown td { 224 | padding: calc(var(--ds-md-zoom) * 6px) calc(var(--ds-md-zoom) * 12px); 225 | border-bottom: 1px solid rgb(var(--ds-rgb-label-3)); 226 | } 227 | 228 | .ds-markdown td:first-child { 229 | padding-left: 0; 230 | } 231 | 232 | .ds-markdown pre { 233 | margin: calc(var(--ds-md-zoom) * 12px) 0; 234 | font-family: var(--ds-font-family-code); 235 | overflow: auto; 236 | } 237 | 238 | .ds-markdown code { 239 | font-size: 0.875em; 240 | font-weight: var(--ds-font-weight-strong); 241 | font-family: var(--ds-font-family-code); 242 | background-color: #ececec; 243 | border-radius: 4px; 244 | padding: 0.15rem 0.3rem; 245 | } 246 | 247 | .ds-markdown code:before { 248 | margin-left: 4px; 249 | } 250 | 251 | .ds-markdown code:after { 252 | margin-right: 4px; 253 | } 254 | 255 | ds-markdown-math { 256 | text-align: center; 257 | display: block; 258 | overflow-x: auto; 259 | overflow-y: hidden; 260 | } 261 | 262 | .ds-markdown > * .ds-markdown-math { 263 | margin: calc(var(--ds-md-zoom) * 12px) 0; 264 | } 265 | 266 | .ds-markdown > * .ds-markdown-math:first-child { 267 | margin-top: 0; 268 | } 269 | 270 | .ds-markdown > * .ds-markdown-math:last-child { 271 | margin-bottom: 0; 272 | } 273 | 274 | .ds-markdown-code-copy-button { 275 | background-color: rgba(var(--ds-rgba-transparent)); 276 | color: inherit; 277 | cursor: pointer; 278 | border: none; 279 | margin: 0; 280 | padding: 0; 281 | } 282 | 283 | .ds-markdown li > .ds-markdown-paragraph { 284 | margin: 4px 0; 285 | } 286 | 287 | .ds-markdown li > :first-child { 288 | margin-top: 0; 289 | } 290 | 291 | .ds-markdown li > :last-child { 292 | margin-bottom: 0; 293 | } 294 | 295 | .ds-markdown .ds-markdown-paragraph:last-child { 296 | margin-bottom: 0 !important; 297 | } 298 | 299 | .ds-markdown > :first-child { 300 | margin-top: 0 !important; 301 | } 302 | 303 | .ds-markdown .ds-typed-thinking { 304 | border-left: 2px solid #e5e5e5; 305 | padding: 0 0 0 12px; 306 | color: #8b8b8b; 307 | line-height: 26px; 308 | font-size: var(--ds-font-size-m); 309 | } 310 | 311 | .ds-markdown.ds-typed-answer { 312 | margin: calc(var(--ds-md-zoom) * 12px) 0; 313 | font-size: var(--ds-md-font-size); 314 | line-height: var(--ds-md-line-height); 315 | } 316 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import MarkdownCMD from './MarkdownCMD/index.js'; 2 | import Markdown from './Markdown/index.js'; 3 | 4 | export default Markdown; 5 | 6 | export { Markdown, MarkdownCMD }; 7 | -------------------------------------------------------------------------------- /src/style.less: -------------------------------------------------------------------------------- 1 | @import './index.less'; 2 | 3 | @import './components/BlockWrap/index.less'; 4 | @import './components/HighReactMarkdown/index.less'; 5 | @import './components/HighReactMarkdown/highlightTheme.less'; 6 | -------------------------------------------------------------------------------- /tsconfig-base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "lib": ["DOM", "ES2020", "DOM.Iterable"], 5 | "jsx": "react-jsx", // Keep this to allow overriding React with Preact 6 | "module": "ES2020", 7 | "moduleResolution": "Node", 8 | "sourceMap": true, 9 | "declaration": true, 10 | "outDir": "./dist", 11 | "esModuleInterop": true, 12 | "forceConsistentCasingInFileNames": true, 13 | "strict": true, 14 | "alwaysStrict": true, 15 | "skipLibCheck": true, 16 | "noUnusedLocals": false, 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tsconfig-cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/cjs", 5 | "module": "CommonJS" 6 | }, 7 | "include": ["src"], 8 | "exclude": ["**/*.test.*"] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig-esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/esm", 5 | "module": "ES2020" 6 | }, 7 | "include": ["src"], 8 | "exclude": ["**/*.test.*"] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig-base.json", 3 | "compilerOptions": { 4 | "resolveJsonModule": true, 5 | "paths": { 6 | "@/test": [ 7 | "./test" 8 | ], 9 | "@/test/*": [ 10 | "./test/*" 11 | ], 12 | }, 13 | "noEmit": true, 14 | "types": [ 15 | "node", 16 | "jest", 17 | "@testing-library/jest-dom", 18 | "@types/jest" 19 | ] 20 | }, 21 | "include": [ 22 | "src", 23 | "test", 24 | "**/*.test.*", 25 | "example" 26 | ], 27 | "exclude": [ 28 | "website" 29 | ] 30 | } -------------------------------------------------------------------------------- /vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /vite.build.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | build: { 8 | lib: { 9 | entry: 'src/index.tsx', 10 | name: 'MyLib', // 你的库名 11 | fileName: (format) => `my-lib.${format}.js`, 12 | formats: ['es', 'cjs'], // 输出格式 13 | }, 14 | rollupOptions: { 15 | // 确保外部化处理你不想打包进库的依赖 16 | external: ['react', 'react-dom', 'remark-gfm', 'react-markdown', 'react-syntax-highlighter'], 17 | output: { 18 | globals: { 19 | react: 'React', 20 | 'react-dom': 'ReactDOM', 21 | }, 22 | }, 23 | }, 24 | }, 25 | }) 26 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | base: '/ds-markdown/', 8 | build: { 9 | sourcemap: false, // 或 'inline', 10 | outDir: 'docs', 11 | }, 12 | }); 13 | --------------------------------------------------------------------------------