通过markdown-it 自定义显示markdown组件,实现代码复制按钮和行号显示
// 安装markdown解析器
yarn add markdown-it
// 安装代码高亮highlight组件
yarn add highlight.js
// 简单声明module 防止ts引入报错
declare module 'markdown-it'
declare module 'highlight.js'
通过在markdown-it
的highlight
中处理高亮时在合适的位置插入如下html
代码
// 复制按钮html
`<div class="toolbar" onclick='window.copyCode("${code}")'><img alt="复制" src="${src}"/><div>${
txt ? txt : ''
}</div></div>`
在window
下挂载全局方法copyCode
window.copyCode = function (str: string) {
const array = JSON.parse(str.replaceAll('\\\\"', '\\"').replaceAll("\\\\'", "\\'"))
var newStr = ''
array.forEach((item: string) => {
newStr += item + '\n'
})
//复制实现
}
获取code
时需要处理换行符和引号
// str 是highlight中源码str: string
JSON.stringify(str.split('\n'))
.replaceAll('\\"', '\\\\"') // \" => \\"
.replaceAll('"', '\\"') // " => \";
.replaceAll("\\'", "\\\\'")
通过在markdown-it
的highlight
中处理高亮时在合适的位置插入如下html
代码
`<ul class="line-box">${lis}</ul>`
获取行号lis
var lis = ''
if (array) {// str 是highlight中源码str.split('\n')分割后集合
for (var i = 1; i <= array.length; i++) {
lis += `<li>${i}</li>`
}
}
import { defineComponent, onMounted, ref, watch } from 'vue'
import MarkdownIt from 'markdown-it'
import 'highlight.js/styles/atom-one-dark.css'
import 'highlight.js/lib/common'
import './index.less'
import hljs from 'highlight.js'
export default defineComponent({
name: 'MarkDownBox',
props: {
message: {
type: String,
default: ''
},
showLine: {
type: Boolean,
defalut: true
}
},
emits: ['copyCode'],
setup(props, { emit, slots }) {
const mdHtml = ref()
window.copyCode = function (str: string) {
const array = JSON.parse(str.replaceAll('\\\\"', '\\"'))
var newStr = ''
array.forEach((item: string) => {
newStr += item + '\n'
})
emit('copyCode', newStr)
}
var md = MarkdownIt({
highlight: function (str: string, lang: string) {
const hasLang = lang && hljs.getLanguage(lang)
const codes = str.split('\n')
const toolBox = getCodeToolBox(hasLang, lang, getCode(codes))
if (hasLang) {
try {
return `<pre>${toolBox}<div class="code-box">
${props.showLine != false ? getLineBox(codes) : ''}
<code class="hljs language-${lang}">${hljs.highlight(lang, str, true).value}</code>
</div></pre>`
} catch (__) {}
}
return `<pre>${toolBox}<div class="code-box">${
props.showLine != false ? getLineBox(codes) : ''
}<code class="hljs">${md.utils.escapeHtml(str)}</code></div></pre>`
}
})
const getCode = (array: Array<string>) => {
return JSON.stringify(array)
.replaceAll('\\"', '\\\\"') // \" => \\"
.replaceAll('"', '\\"') // " => \";
}
const getLineBox = (array: Array<string>) => {
var lis = ''
if (array) {
for (var i = 1; i <= array.length; i++) {
lis += `<li>${i}</li>`
}
}
return `<ul class="line-box">${lis}</ul>`
}
const getCodeToolBox = (hasLang: boolean, lang: string, code: string) => {
return `<div class="tool-box" >
${hasLang ? ` <div class="code-lang">${lang}</div>` : ''}
${getCodeTool(`window.copyCode(\"${code}\")`, require('@/assets/images/icon_copy.png'), '复制')}
</div>`
}
const getCodeTool = (clickFun: string, src: string, txt: string) => {
return `<div class="toolbar" onclick='${clickFun}'><img alt="复制" src="${src}"/><div>${
txt ? txt : ''
}</div></div>`
}
watch(
() => props.message,
(val) => {
markdown(val)
}
)
const markdown = (msg: string) => {
mdHtml.value = md.render( msg )
}
onMounted(() => {
markdown(props.message)
})
return () => {
return (
<div
v-html={mdHtml.value}
</div>
)
}
}
})
pre {
display: flex;
flex-direction: column;
width: 100%;
margin: 10px 0px;
border: none;
border-radius: 6px;
background-color: rgb(34, 34, 34) !important;
overflow-x: auto;
color: #fff;
}
pre code {
background-color: #292b33 !important;
padding: 12px;
width: 100%;
}
.code-box {
display: flex;
width: 100%;
}
.line-box {
color: #cdcdcd;
text-align: right;
padding: 12px 8px !important;
font-size: 14px;
font-family: 'Courier New', Courier, monospace;
border-right: 1px solid #7d7d7d;
}
.tool-box {
width: 100%;
padding: 4px 12px;
display: flex;
align-items: center;
justify-content: flex-end;
color: white;
font-size: 14px;
.code-lang {
flex: auto;
}
.toolbar {
display: flex;
align-items: center;
padding-left: 4px;
img {
width: 18px;
height: 18px;
margin-right: 1px;
}
}
}