实现代码高亮

This commit is contained in:
锦恢 2025-04-08 17:07:32 +08:00
parent c026b17fdc
commit 15c8b6d7a0
14 changed files with 768 additions and 96 deletions

View File

@ -6,7 +6,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.svg"> <link rel="icon" href="<%= BASE_URL %>favicon.svg">
<link rel="stylesheet" href="default-light.css"> <link rel="stylesheet" href="default-dark.css">
<link rel="stylesheet" href="vscode.css"> <link rel="stylesheet" href="vscode.css">
<link rel="stylesheet" href="mcp.css"> <link rel="stylesheet" href="mcp.css">
<link rel="stylesheet" href="iconfont.css"> <link rel="stylesheet" href="iconfont.css">

View File

@ -10,8 +10,10 @@
--sidebar-item-margin: 5px; --sidebar-item-margin: 5px;
--toolbar-height: 50px; --toolbar-height: 50px;
--toolbar-item-height: 32px; --toolbar-item-height: 32px;
--font-size-xsp: 11px;
--md-zoom: 1.12;
--code-font-family: "Cascadia code"; --code-font-family: Consolas, Monaco, "Cascadia code", 'Andale Mono', 'Ubuntu Mono', monospace;
/* css 动画属性 */ /* css 动画属性 */
--animation-7s: .7s cubic-bezier(0.23, 1, 0.32, 1); --animation-7s: .7s cubic-bezier(0.23, 1, 0.32, 1);
@ -37,46 +39,6 @@ body::-webkit-scrollbar {
width: 0; width: 0;
} }
* hr {
border: none;
background-color: var(--main-color);
height: 1.5px;
width: 95%;
}
/*
::-webkit-scrollbar {
width: 12px;
}
::-webkit-scrollbar-track {
background: var(--sidebar);
}
::-webkit-scrollbar-thumb {
background: var(--scrollbar-background);
border-radius: .3em;
}
::-webkit-scrollbar-thumb:hover {
background: var(--scrollbar-hover);
border-radius: .3em;
}
::-webkit-scrollbar-thumb:active {
background: var(--scrollbar-active);
border-radius: .3em;
}
::-webkit-scrollbar-button {
height: 0;
width: 0;
}
::-webkit-scrollbar-corner {
background: none;
display: none;
} */
.el-select__wrapper { .el-select__wrapper {
min-width: 100px; min-width: 100px;
@ -134,12 +96,80 @@ a {
} }
.el-textarea__inner::-webkit-scrollbar-thumb { .el-textarea__inner::-webkit-scrollbar-thumb {
background: #cb81da; background: var(--main-color);
/* 使用您的主题色 */ /* 使用您的主题色 */
border-radius: 4px; border-radius: 4px;
} }
.el-textarea__inner::-webkit-scrollbar-thumb:hover { .el-textarea__inner::-webkit-scrollbar-thumb:hover {
background: var(--main-color);
/* 深紫色悬停状态 */
}
* hr {
border: none;
width: 95%;
background-color: var(--background);
height: 1px;
}
.openmcp-code-block {
display: flex;
flex-direction: column;
margin-bottom: 1em;
border: 1px solid var(--main-color);
border-radius: 4px;
overflow: hidden;
font-size: 12px;
}
.openmcp-code-block pre code::-webkit-scrollbar {
width: 8px;
height: 5px;
}
.openmcp-code-block pre code::-webkit-scrollbar-track {
background: transparent;
/* 浅紫色背景 */
border-radius: 4px;
}
.openmcp-code-block pre code::-webkit-scrollbar-thumb {
background: #cb81da;
/* 使用您的主题色 */
border-radius: 4px;
}
.openmcp-code-block pre code::-webkit-scrollbar-thumb:hover {
background: #a259b5; background: #a259b5;
/* 深紫色悬停状态 */ /* 深紫色悬停状态 */
} }
.code-header {
display: flex;
justify-content: space-between;
align-items: center;
color: var(--sidebar);
background-color: var(--main-color);
padding: 5px 10px;
}
.lang-id {
font-weight: bold;
}
.copy-button {
cursor: pointer;
padding: 0.2em 0.5em;
border: none;
border-radius: 4px;
background-color: transparent;
color: var(--sidebar);
transition: all 0.3s ease;
}
.copy-button:hover {
background-color: var(--main-color);
color: #fff;
}

View File

@ -48,8 +48,6 @@ onMounted(() => {
connectionResult.logString = msg; connectionResult.logString = msg;
}, { once: true }); }, { once: true });
setTimeout(() => { setTimeout(() => {
// //
loadSetting(); loadSetting();

View File

@ -21,7 +21,7 @@
<div class="message-content" v-else-if="message.role === 'assistant'"> <div class="message-content" v-else-if="message.role === 'assistant'">
<div class="message-role">Agent</div> <div class="message-role">Agent</div>
<div class="message-text"> <div class="message-text">
<div v-html="markdownToHtml(message.content)" ref="markdownContent"></div> <div v-html="markdownToHtml(message.content)"></div>
<span class="iconfont icon-copy" @click="handleCopy(message.content)"></span> <span class="iconfont icon-copy" @click="handleCopy(message.content)"></span>
</div> </div>
</div> </div>
@ -36,13 +36,17 @@
</div> </div>
<!-- 正在加载的部分实时解析 markdown -->
<div v-if="isLoading" class="message-item assistant"> <div v-if="isLoading" class="message-item assistant">
<div class="message-avatar"> <div class="message-avatar">
<span class="iconfont icon-chat"></span> <span class="iconfont icon-chat"></span>
</div> </div>
<div class="message-content"> <div class="message-content">
<div class="message-role">Agent</div> <div class="message-role">Agent</div>
<div class="message-text">{{ streamingContent }}<span class="typing-cursor">|</span></div> <div class="message-text">
<span v-html="markdownToHtml(streamingContent)"></span>
<span class="typing-cursor">|</span>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -193,7 +197,6 @@ const handleSend = () => {
} }
if (streamingContent.value) { if (streamingContent.value) {
// //
console.log(tabStorage);
tabStorage.messages.push({ tabStorage.messages.push({
role: 'assistant', role: 'assistant',
@ -381,9 +384,5 @@ onUnmounted(() => {
margin-top: 0.2em; margin-top: 0.2em;
margin-bottom: 0.2em; margin-bottom: 0.2em;
} }
</style>
<style>
/* 引入 KaTeX 样式 */
@import 'katex/dist/katex.min.css';
</style> </style>

View File

@ -0,0 +1,57 @@
import Prism from "./prism";
// 定义 escapeHtml 函数
function escapeHtml(unsafe: string) {
return unsafe
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
// 导出默认的 highlight 函数
export default function highlight(str: string, lang: string) {
// 创建代码块容器
let container = `<div class="openmcp-code-block">`;
// 添加复制按钮(右上角)
container += `
<div class="code-header">
<div class="code-language">${lang || ''}</div>
<button class="copy-button" onclick="copyCode(this)"></button>
</div>
`;
if (lang && Prism.languages[lang]) {
// 使用 Prism 高亮代码
const highlightedCode = Prism.highlight(str, Prism.languages[lang], lang);
// 添加代码区域
container += `<pre class="language-${lang}"><code class="language-${lang}">${highlightedCode}</code></pre>`;
} else {
// 普通代码块
container += `<pre class="language-none"><code>${escapeHtml(str)}</code></pre>`;
}
container += `</div>`;
return container;
}
// 全局复制函数
(window as any).copyCode = function (button: HTMLElement) {
const codeBlock = button.closest('.openmcp-code-block');
if (!codeBlock) return;
const codeElement = codeBlock.querySelector('code');
const code = codeElement?.textContent || '';
navigator.clipboard.writeText(code).then(() => {
const originalText = button.textContent;
button.textContent = '已复制';
setTimeout(() => {
button.textContent = originalText;
}, 500);
}).catch((error) => {
console.error('复制失败:', error);
button.textContent = '复制失败';
});
};

View File

@ -1 +0,0 @@
declare module 'markdown-it-katex';

View File

@ -0,0 +1,76 @@
import katex from 'katex';
import type MarkdownIt from "markdown-it";
// 定义 delimiter 对象的类型
type Delimiter = {
left: string;
right: string;
display: boolean;
};
// 定义 defaultOptions 对象的类型
type DefaultOptions = {
delimiters: Delimiter[];
};
// 定义 defaultOptions 常量
const defaultOptions: DefaultOptions = {
delimiters: [
{ left: '\\[', right: '\\]', display: true },
{ left: '\\(', right: '\\)', display: false },
],
};
// 定义 escapedBracketRule 函数
function escapedBracketRule(options: DefaultOptions): any {
return (state: any, silent: boolean): boolean => {
const max: number = state.posMax;
const start: number = state.pos;
for (const { left, right, display } of options.delimiters) {
// 检查是否以左标记开始
if (!state.src.slice(start).startsWith(left)) continue;
// 跳过左标记的长度
let pos: number = start + left.length;
// 寻找匹配的右标记
while (pos < max) {
if (state.src.slice(pos).startsWith(right)) {
break;
}
pos++;
}
// 没找到匹配的右标记,跳过,进入下个匹配
if (pos >= max) continue;
// 如果不是静默模式,将 LaTeX 公式转换为 MathML
if (!silent) {
const content: string = state.src.slice(start + left.length, pos);
try {
const renderedContent: string = katex.renderToString(content, {
throwOnError: false,
output: 'mathml',
displayMode: display,
});
const token = state.push('html_inline', '', 0);
token.content = renderedContent;
} catch (e) {
console.error(e);
}
}
// 更新位置,跳过右标记的长度
state.pos = pos + right.length;
return true;
}
return false;
};
}
// 定义主函数
export default function (md: MarkdownIt, options: DefaultOptions = defaultOptions): void {
md.inline.ruler.after('text', 'escaped_bracket', escapedBracketRule(options));
}

View File

@ -1,13 +1,17 @@
import MarkdownIt from 'markdown-it'; import MarkdownIt from 'markdown-it';
import katex from 'katex'; import MarkdownKatex from './markdown-katex';
import markdownItKatex from 'markdown-it-katex'; import MarkdownHighlight from './markdown-highlight';
import { pinkLog } from '@/views/setting/util';
const md = new MarkdownIt(); const md = new MarkdownIt({
highlight: MarkdownHighlight,
});
md.use(markdownItKatex, { md.use(MarkdownKatex, {
throwOnError: false, delimiters: [
errorColor: '#cc0000' { left: '\\[', right: '\\]', display: true },
{ left: '\\(', right: '\\)', display: false },
{ left: '$$', right: '$$', display: false },
],
}); });
export const markdownToHtml = (markdown: string) => { export const markdownToHtml = (markdown: string) => {

View File

@ -0,0 +1,6 @@
const Prism: {
highlight: (code: string, language: any, lang: string) => string;
languages: Record<string, any>;
// 添加其他需要的类型定义
};
export default Prism;

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,432 @@
.vscode-dark code[class*=language-],
.vscode-dark pre[class*=language-] {
color: #abb2bf;
font-family: var(--code-font-family);
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
.vscode-dark pre[class*=language-] {
position: relative;
margin: .5em 0;
overflow: visible;
padding: 1px;
}
.vscode-dark pre[class*=language-]>code {
position: relative;
background-size: 3em 3em;
background-origin: content-box;
background-attachment: local;
padding: 0 5px;
}
.vscode-dark code[class*=language-] {
max-height: inherit;
height: inherit;
display: block;
overflow: auto;
}
.vscode-dark :not(pre)>code[class*=language-],
.vscode-dark pre[class*=language-] {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
margin-bottom: 1em;
}
.vscode-dark :not(pre)>code[class*=language-] {
position: relative;
padding: .2em;
border-radius: .3em;
color: #c92c2c;
border: 1px solid rgba(0, 0, 0, .1);
display: inline;
white-space: normal;
}
.vscode-dark pre[class*=language-]:after,
.vscode-dark pre[class*=language-]:before {
content: '';
display: block;
position: absolute;
bottom: .75em;
left: .18em;
-webkit-transform: rotate(-2deg);
-moz-transform: rotate(-2deg);
-ms-transform: rotate(-2deg);
-o-transform: rotate(-2deg);
transform: rotate(-2deg);
}
.vscode-dark pre[class*=language-]:after {
right: .75em;
left: auto;
-webkit-transform: rotate(2deg);
-moz-transform: rotate(2deg);
-ms-transform: rotate(2deg);
-o-transform: rotate(2deg);
transform: rotate(2deg);
}
.vscode-dark .token.block-comment,
.vscode-dark .token.cdata,
.vscode-dark .token.comment,
.vscode-dark .token.doctype,
.vscode-dark .token.prolog {
color: #7F848E;
}
.vscode-dark .token.punctuation {
color: #D19A66;
}
.vscode-dark .token.boolean,
.vscode-dark .token.constant,
.vscode-dark .token.deleted,
.vscode-dark .token.function-name,
.vscode-dark .token.property,
.vscode-dark .token.symbol,
.vscode-dark .token.tag {
color: #d55f69;
}
.vscode-dark .token.number {
color: #d19a66;
}
.vscode-dark .token.attr-name,
.vscode-dark .token.builtin,
.vscode-dark .token.char,
.vscode-dark .token.inserted,
.vscode-dark .token.string {
color: #4da736;
}
.vscode-dark .token.selector {
color: #af7137;
}
.vscode-dark .token.function {
color: #61afef;
}
.vscode-dark .token.entity,
.vscode-dark .token.operator,
.vscode-dark .token.url {
color: #56B6C2;
}
.vscode-dark .token.variable {
color: #E06C75;
}
.vscode-dark .token.atrule,
.vscode-dark .token.attr-value {
color: #61AFEF;
}
.vscode-dark .token.class-name {
color: #9e32d4;
}
.vscode-dark .token.keyword {
color: #c586c0;
}
.vscode-dark .token.important,
.vscode-dark .token.regex {
color: #e90;
}
.vscode-dark .language-css .token.string,
.vscode-dark .style .token.string {
color: #a67f59;
}
.vscode-dark .token.important {
font-weight: 400;
}
.vscode-dark .token.bold {
font-weight: 700;
}
.vscode-dark .token.italic {
font-style: italic;
}
.vscode-dark .token.entity {
cursor: help;
}
.vscode-dark .token.namespace {
color: #9e32d4;
opacity: .7;
}
@media screen and (max-width:767px) {
.vscode-dark pre[class*=language-]:after,
.vscode-dark pre[class*=language-]:before {
bottom: 14px;
box-shadow: none;
}
}
.vscode-dark pre[class*=language-].line-numbers.line-numbers {
padding-left: 0;
}
.vscode-dark pre[class*=language-].line-numbers.line-numbers code {
padding-left: 3.8em;
}
.vscode-dark pre[class*=language-].line-numbers.line-numbers .line-numbers-rows {
left: 0;
}
.vscode-dark pre[class*=language-][data-line] {
padding-top: 0;
padding-bottom: 0;
padding-left: 0;
}
.vscode-dark pre[data-line] code {
position: relative;
padding-left: 4em;
}
.vscode-dark pre .line-highlight {
margin-top: 0;
}
.vscode-light code[class*=language-],
.vscode-light pre[class*=language-] {
color: #383A42;
font-family: var(--code-font-family);
font-size: 1em;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
.vscode-light pre[class*=language-] {
position: relative;
margin: .5em 0;
overflow: visible;
padding: 1px;
}
.vscode-light pre[class*=language-]>code {
position: relative;
background-origin: content-box;
background-attachment: local;
padding: 0 5px;
}
.vscode-light code[class*=language-] {
max-height: inherit;
height: inherit;
display: block;
overflow: auto;
}
.vscode-light :not(pre)>code[class*=language-],
.vscode-light pre[class*=language-] {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
margin-bottom: 1em;
}
.vscode-light :not(pre)>code[class*=language-] {
position: relative;
padding: .2em;
border-radius: .3em;
color: #c92c2c;
border: 1px solid rgba(0, 0, 0, .1);
display: inline;
white-space: normal;
}
.vscode-light pre[class*=language-]:after,
.vscode-light pre[class*=language-]:before {
content: '';
display: block;
position: absolute;
bottom: .75em;
left: .18em;
width: 40%;
height: 20%;
max-height: 13em;
-webkit-transform: rotate(-2deg);
-moz-transform: rotate(-2deg);
-ms-transform: rotate(-2deg);
-o-transform: rotate(-2deg);
transform: rotate(-2deg);
}
.vscode-light pre[class*=language-]:after {
right: .75em;
left: auto;
-webkit-transform: rotate(2deg);
-moz-transform: rotate(2deg);
-ms-transform: rotate(2deg);
-o-transform: rotate(2deg);
transform: rotate(2deg);
}
.vscode-light .token.block-comment,
.vscode-light .token.cdata,
.vscode-light .token.comment,
.vscode-light .token.doctype,
.vscode-light .token.prolog {
color: #7F848E;
}
.vscode-light .token.punctuation {
color: #D19A66;
}
.vscode-light .token.boolean,
.vscode-light .token.constant,
.vscode-light .token.deleted,
.vscode-light .token.function-name,
.vscode-light .token.property,
.vscode-light .token.symbol,
.vscode-light .token.tag {
color: #d55f69;
}
.vscode-light .token.number {
color: #be8550;
}
.vscode-light .token.attr-name,
.vscode-light .token.builtin,
.vscode-light .token.char,
.vscode-light .token.inserted,
.vscode-light .token.string {
color: #4da736;
}
.vscode-light .token.selector {
color: #af7137;
}
.vscode-light .token.function {
color: #4284f5;
}
.vscode-light .token.entity,
.vscode-light .token.operator,
.vscode-light .token.url {
color: #56B6C2;
}
.vscode-light .token.variable {
color: #E06C75;
}
.vscode-light .token.atrule,
.vscode-light .token.attr-value {
color: #61AFEF;
}
.vscode-light .token.class-name {
color: #9e32d4;
}
.vscode-light .token.keyword {
color: #c37429;
}
.vscode-light .token.important,
.vscode-light .token.regex {
color: #e90;
}
.vscode-light .language-css .token.string,
.vscode-light .style .token.string {
color: #a67f59;
}
.vscode-light .token.important {
font-weight: 400;
}
.vscode-light .token.bold {
font-weight: 700;
}
.vscode-light .token.italic {
font-style: italic;
}
.vscode-light .token.entity {
cursor: help;
}
.vscode-light .token.namespace {
color: #9e32d4;
opacity: .7;
}
@media screen and (max-width:767px) {
.vscode-light pre[class*=language-]:after,
.vscode-light pre[class*=language-]:before {
bottom: 14px;
box-shadow: none;
}
}
.vscode-light pre[class*=language-].line-numbers.line-numbers {
padding-left: 0;
}
.vscode-light pre[class*=language-].line-numbers.line-numbers code {
padding-left: 3.8em;
}
.vscode-light pre[class*=language-].line-numbers.line-numbers .line-numbers-rows {
left: 0;
}
.vscode-light pre[class*=language-][data-line] {
padding-top: 0;
padding-bottom: 0;
padding-left: 0;
}
.vscode-light pre[data-line] code {
position: relative;
padding-left: 4em;
}
.vscode-light pre .line-highlight {
margin-top: 0;
}

View File

@ -50,6 +50,34 @@ export function setDefaultCss() {
} else { } else {
setExtraDarkColorCss(); setExtraDarkColorCss();
} }
const acquireVsCodeApi = (window as any)['acquireVsCodeApi']
const mode = acquireVsCodeApi === undefined ? 'debug' : 'release';
if (mode === 'debug') {
// 判断颜色深浅,模拟 .vscode-dark 的加入
const theme = getThemeColor();
const app = document.getElementById('app');
app?.classList.add('vscode-' + theme);
}
}
let themeColor: 'light' | 'dark' | undefined = undefined;
export function getThemeColor(): 'light' | 'dark' {
if (themeColor) {
return themeColor;
}
const rootStyles = getComputedStyle(document.documentElement);
const backgroundColorString = rootStyles.getPropertyValue('--background');
const backgroundColor = parseColor(backgroundColorString);
if (backgroundColor) {
const isLight = isLightColorTheme(backgroundColor.r, backgroundColor.g, backgroundColor.b);
themeColor = isLight ? 'light' : 'dark';
return themeColor;
}
return 'dark';
} }
function setExtraLightColorCss() { function setExtraLightColorCss() {

View File

@ -2,8 +2,10 @@ import { createApp } from "vue";
import App from "./App.vue"; import App from "./App.vue";
import router from "./router"; import router from "./router";
import i18n from './i18n'; import i18n from './i18n';
import ElementPlus from 'element-plus' import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css' import 'element-plus/dist/index.css';
import "@/components/main-panel/chat/prism/prism.css";
import 'katex/dist/katex.min.css';
createApp(App) createApp(App)
.use(i18n) .use(i18n)

View File

@ -61,40 +61,6 @@
} }
} }
}, },
{
"name": "交互测试",
"icon": "icon-robot",
"type": "blank",
"componentIndex": 3,
"storage": {
"messages": [
{
"role": "user",
"content": "如何在 typescript 项目中使用 markdown-it"
},
{
"role": "assistant",
"content": "# 在 TypeScript 项目中使用 markdown-it\n\nmarkdown-it 是一个流行的 Markdown 解析器,可以很方便地在 TypeScript 项目中使用。以下是完整的配置和使用指南:\n\n## 1. 安装依赖\n\n首先安装 markdown-it 及其 TypeScript 类型定义:\n\n```bash\nnpm install markdown-it\nnpm install --save-dev @types/markdown-it\n```\n\n## 2. 基本使用\n\n创建一个 TypeScript 文件(如 `markdown.ts`)并添加以下代码:\n\n```typescript\nimport MarkdownIt from 'markdown-it';\n\n// 创建 markdown-it 实例\nconst md = new MarkdownIt();\n\n// 渲染 Markdown 字符串\nconst result = md.render('# Hello markdown-it in TypeScript!');\nconsole.log(result);\n```\n\n## 3. 配置选项\n\n你可以自定义 markdown-it 的配置:\n\n```typescript\nconst md = new MarkdownIt({\n html: true, // 启用 HTML 标签\n linkify: true, // 自动将 URL 转换为链接\n typographer: true, // 启用排版美化\n breaks: true // 将换行符转换为 <br>\n});\n```\n\n## 4. 添加插件\n\nmarkdown-it 有许多插件可以扩展功能。例如添加 markdown-it-anchor\n\n```bash\nnpm install markdown-it-anchor\nnpm install --save-dev @types/markdown-it-anchor\n```\n\n然后在代码中使用\n\n```typescript\nimport MarkdownIt from 'markdown-it';\nimport anchor from 'markdown-it-anchor';\n\nconst md = new MarkdownIt().use(anchor, {\n permalink: true,\n permalinkBefore: true\n});\n\nconst result = md.render('# Section Title\\n\\nSome content');\nconsole.log(result);\n```\n\n## 5. 自定义渲染规则\n\n你可以覆盖默认的渲染规则\n\n```typescript\nconst md = new MarkdownIt();\n\n// 覆盖默认的链接渲染\nmd.renderer.rules.link_open = (tokens, idx, options, env, self) => {\n const token = tokens[idx];\n token.attrSet('target', '_blank');\n token.attrSet('rel', 'noopener noreferrer');\n return self.renderToken(tokens, idx, options);\n};\n\nconst result = md.render('[Open in new tab](https://example.com)');\n```\n\n## 6. 在 React/Vue 等框架中使用\n\n### 在 React 中使用:\n\n```typescript\nimport React from 'react';\nimport MarkdownIt from 'markdown-it';\n\nconst md = new MarkdownIt();\n\nconst MarkdownRenderer: React.FC<{ content: string }> = ({ content }) => {\n return <div dangerouslySetInnerHTML={{ __html: md.render(content) }} />;\n};\n\n// 使用\n<MarkdownRenderer content=\"# Hello React with markdown-it\" />\n```\n\n### 在 Vue 中使用:\n\n```typescript\nimport { defineComponent } from 'vue';\nimport MarkdownIt from 'markdown-it';\n\nconst md = new MarkdownIt();\n\nexport default defineComponent({\n props: ['content'],\n setup(props) {\n return () => (\n <div domPropsInnerHTML={md.render(props.content)} />\n );\n }\n});\n```\n\n## 7. 处理安全性\n\n如果需要处理用户输入的 Markdown建议使用 markdown-it-sanitizer 或其他安全插件:\n\n```bash\nnpm install markdown-it-sanitizer\n```\n\n```typescript\nimport MarkdownIt from 'markdown-it';\nimport sanitizer from 'markdown-it-sanitizer';\n\nconst md = new MarkdownIt().use(sanitizer);\n```\n\n## 8. 完整示例\n\n下面是一个完整的 TypeScript 类示例:\n\n```typescript\nimport MarkdownIt from 'markdown-it';\nimport anchor from 'markdown-it-anchor';\nimport sanitizer from 'markdown-it-sanitizer';\n\nclass MarkdownRenderer {\n private md: MarkdownIt;\n\n constructor() {\n this.md = new MarkdownIt({\n html: true,\n linkify: true,\n typographer: true\n })\n .use(anchor)\n .use(sanitizer);\n }\n\n public render(markdown: string): string {\n return this.md.render(markdown);\n }\n\n public renderInline(markdown: string): string {\n return this.md.renderInline(markdown);\n }\n}\n\n// 使用示例\nconst renderer = new MarkdownRenderer();\nconst html = renderer.render('# Title\\n\\nSome *markdown* content');\nconsole.log(html);\n```\n\n通过以上步骤你就可以在 TypeScript 项目中轻松使用 markdown-it 来解析和渲染 Markdown 内容了。"
},
{
"role": "assistant",
"content": "康托洛维奇不等式Kantorovich Inequality的数学公式为\n\n对于正定矩阵 \\( A \\) 和任意非零向量 \\( x \\),有\n\n\\[\n\\frac{(x^\\top A x)(x^\\top A^{-1} x)}{(x^\\top x)^2} \\leq \\frac{(\\lambda_{\\max} + \\lambda_{\\min})^2}{4 \\lambda_{\\max} \\lambda_{\\min}},\n\\]\n\n其中 \\( \\lambda_{\\max} \\) 和 \\( \\lambda_{\\min} \\) 分别是矩阵 \\( A \\) 的最大和最小特征值。"
},
{
"role": "assistant",
"content": "调和平均数Harmonic Mean的数学公式为\n\n对于正数 \\( x_1, x_2, \\dots, x_n \\),调和平均数 \\( H \\) 定义为:\n\n\\[\nH = \\frac{n}{\\frac{1}{x_1} + \\frac{1}{x_2} + \\cdots + \\frac{1}{x_n}}.\n\\]"
}
],
"settings": {
"modelIndex": 0,
"enableTools": true,
"enableWebSearch": false,
"temperature": 0.7,
"contextLength": 10,
"systemPrompt": ""
}
}
},
{ {
"name": "交互测试", "name": "交互测试",
"icon": "icon-robot", "icon": "icon-robot",
@ -128,6 +94,40 @@
"systemPrompt": "" "systemPrompt": ""
} }
} }
},
{
"name": "交互测试",
"icon": "icon-robot",
"type": "blank",
"componentIndex": 3,
"storage": {
"messages": [
{
"role": "user",
"content": "如何让 mardown-it 支持渲染出来的编程语言自带语法高亮"
},
{
"role": "assistant",
"content": "要让 `markdown-it` 渲染的代码块自带语法高亮,你可以通过以下步骤实现:\n\n---\n\n### 1. **安装必要的依赖**\n首先确保已安装 `markdown-it` 和语法高亮库(如 `highlight.js` 或 `prismjs`\n```bash\nnpm install markdown-it highlight.js\n# 或\nnpm install markdown-it prismjs\n```\n\n---\n\n### 2. **配置 markdown-it 启用代码高亮**\n#### 方案一:使用 `highlight.js`(推荐)\n```javascript\nconst md = require('markdown-it')({\n highlight: function (str, lang) {\n if (lang && require('highlight.js').getLanguage(lang)) {\n try {\n return require('highlight.js').highlight(str, { language: lang }).value;\n } catch (__) {}\n }\n return ''; // 使用默认转义\n }\n});\n```\n\n#### 方案二:使用 `prismjs`\n```javascript\nconst md = require('markdown-it')({\n highlight: function (str, lang) {\n if (lang && require('prismjs').languages[lang]) {\n try {\n return require('prismjs').highlight(str, require('prismjs').languages[lang], lang);\n } catch (__) {}\n }\n return ''; // 使用默认转义\n }\n});\n```\n\n---\n\n### 3. **引入高亮样式**\n语法高亮需要对应的 CSS 样式文件,可以通过以下方式引入:\n#### 对于 `highlight.js`\n- 从 [highlight.js 主题库](https://highlightjs.org/static/demo/) 选择样式,然后在 HTML 中引入:\n ```html\n <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/atom-one-dark.min.css\">\n ```\n\n#### 对于 `prismjs`\n- 从 [Prism 主题库](https://prismjs.com/) 下载样式,或在 HTML 中引入:\n ```html\n <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css\">\n ```\n\n---\n\n### 4. **渲染 Markdown 内容**\n```javascript\nconst result = md.render('```js\\nconsole.log(\"Hello\");\\n```');\nconsole.log(result);\n```\n输出会包含语法高亮的 HTML如 `<span class=\"hljs-keyword\">console</span>`)。\n\n---\n\n### 5. **可选:自定义高亮逻辑**\n如果需要更复杂的处理如行号、错误提示可以修改 `highlight` 函数:\n```javascript\nhighlight: function (str, lang) {\n if (lang === 'mermaid') {\n return `<div class=\"mermaid\">${str}</div>`; // 特殊处理 Mermaid 图表\n }\n // ...默认高亮逻辑\n}\n```\n\n---\n\n### 6. **在浏览器中使用**\n如果在前端使用确保通过 CDN 或打包工具引入库:\n```html\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/markdown-it/13.0.2/markdown-it.min.js\"></script>\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js\"></script>\n<script>\n const md = window.markdownit({\n highlight: (str, lang) => {\n if (lang && hljs.getLanguage(lang)) {\n return hljs.highlight(str, { language: lang }).value;\n }\n return '';\n }\n });\n</script>\n```\n\n---\n\n### 常见问题\n- **语言未高亮?** 检查是否安装了对应的语言包(如 `highlight.js` 可能需要额外加载语言模块)。\n- **样式不生效?** 确认 CSS 文件路径正确且未被其他样式覆盖。\n\n通过以上步骤`markdown-it` 渲染的代码块将自动支持语法高亮。"
},
{
"role": "user",
"content": "markdown-it 如何让生成的"
},
{
"role": "assistant",
"content": "要让 `markdown-it` 生成的代码块支持语法高亮,你需要结合语法高亮库(如 `highlight.js` 或 `Prism.js`)进行配置。以下是详细步骤:\n\n---\n\n## **1. 安装依赖**\n首先确保安装了 `markdown-it` 和语法高亮库(如 `highlight.js` 或 `Prism.js`\n\n### **使用 `highlight.js`**\n```bash\nnpm install markdown-it highlight.js\n```\n### **使用 `Prism.js`**\n```bash\nnpm install markdown-it prismjs\n```\n\n---\n\n## **2. 配置 `markdown-it` 启用语法高亮**\n### **方案 1使用 `highlight.js`(推荐)**\n```javascript\nconst md = require(\"markdown-it\")({\n highlight: function (str, lang) {\n if (lang && require(\"highlight.js\").getLanguage(lang)) {\n try {\n return require(\"highlight.js\").highlight(str, { language: lang }).value;\n } catch (err) {\n console.error(\"语法高亮失败:\", err);\n }\n }\n return \"\"; // 使用默认的转义\n },\n});\n```\n### **方案 2使用 `Prism.js`**\n```javascript\nconst md = require(\"markdown-it\")({\n highlight: function (str, lang) {\n if (lang && require(\"prismjs\").languages[lang]) {\n try {\n return require(\"prismjs\").highlight(str, require(\"prismjs\").languages[lang], lang);\n } catch (err) {\n console.error(\"语法高亮失败:\", err);\n }\n }\n return \"\"; // 使用默认的转义\n },\n});\n```\n\n---\n\n## **3. 引入高亮样式**\n语法高亮需要对应的 CSS 样式文件,你可以:\n### **`highlight.js` 样式**\n```html\n<!-- 选择一个主题,如 atom-one-dark -->\n<link\n rel=\"stylesheet\"\n href=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/atom-one-dark.min.css\"\n/>\n```\n### **`Prism.js` 样式**\n```html\n<!-- 选择一个主题,如 prism-tomorrow -->\n<link\n rel=\"stylesheet\"\n href=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css\"\n/>\n```\n\n---\n\n## **4. 渲染 Markdown**\n```javascript\nconst markdownText = `\n\\`\\`\\`javascript\nconsole.log(\"Hello, World!\");\n\\`\\`\\`\n`;\n\nconst htmlOutput = md.render(markdownText);\nconsole.log(htmlOutput);\n```\n输出\n```html\n<pre><code class=\"language-javascript\"><span class=\"hljs-built_in\">console</span>.<span class=\"hljs-title function_\">log</span>(<span class=\"hljs-string\">\"Hello, World!\"</span>);</code></pre>\n```\n\n---\n\n## **5. 在浏览器中使用**\n如果你在前端使用 `markdown-it`,可以这样加载:\n```html\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/markdown-it/13.0.2/markdown-it.min.js\"></script>\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js\"></script>\n<script>\n const md = window.markdownit({\n highlight: function (str, lang) {\n if (lang && hljs.getLanguage(lang)) {\n try {\n return hljs.highlight(str, { language: lang }).value;\n } catch (err) {\n console.error(\"语法高亮失败:\", err);\n }\n }\n return \"\";\n },\n });\n const html = md.render(\"```js\\nconsole.log('Hello');\\n```\");\n document.body.innerHTML = html;\n hljs.highlightAll(); // 确保动态加载的代码块也能高亮\n</script>\n```\n\n---\n\n## **6. 高级用法**\n### **1自定义高亮**\n```javascript\nhighlight: function (str, lang) {\n if (lang === \"mermaid\") {\n return `<div class=\"mermaid\">${str}</div>`; // 特殊处理 Mermaid 图表\n }\n // 其他语言的高亮逻辑\n // ...\n}\n```\n### **2行号支持**\n使用 `highlight.js` + `highlightjs-line-numbers.js`\n```html\n<script src=\"https://cdnjs.cloudflare.com/ajax/libs/highlightjs-line-numbers.js/2.8.0/highlightjs-line-numbers.min.js\"></script>\n<script>\n hljs.highlightAll();\n hljs.initLineNumbersOnLoad();\n</script>\n```\n\n---\n\n## **7. 常见问题**\n### **1某些语言不高亮**\n- `highlight.js` 默认只支持部分语言,其他语言需要额外引入:\n ```html\n <script src=\"https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/go.min.js\"></script>\n ```\n- `Prism.js` 需要手动加载语言:\n ```javascript\n require(\"prismjs/components/prism-go\");\n ```\n\n### **2样式不生效**\n- 确保 CSS 文件正确加载。\n- 检查是否有其他样式覆盖。\n\n---\n\n## **总结**\n| 步骤 | 操作 |\n|------|------|\n| 1. 安装依赖 | `npm install markdown-it highlight.js` |\n| 2. 配置高亮 | 在 `markdown-it` 的 `highlight` 回调中使用 `highlight.js` 或 `Prism.js` |\n| 3. 引入样式 | 加载对应的 CSS 文件 |\n| 4. 渲染 Markdown | `md.render(\"```js\\nconsole.log('Hi');\\n```\")` |\n| 5. 浏览器支持 | 确保 `hljs.highlightAll()` 执行 |\n\n这样`markdown-it` 生成的代码块就会自动带有语法高亮! 🎨"
}
],
"settings": {
"modelIndex": 0,
"enableTools": true,
"enableWebSearch": false,
"temperature": 0.7,
"contextLength": 10,
"systemPrompt": ""
}
}
} }
] ]
} }