解决Vue3项目中Markdown文件展示的问题
2024-12-24 08:59:31

最近在做前端项目,需要MD文件的录入及展示效果。

然后从录入组件到展示,出乎意料的麻烦。

倒不是这事儿实现的思路多难,而是实现过程中有很多出乎意料的岔道。

正文

本来一开始打算使用wangeEditor直接解决用户录入富文本的需求。

但是想了一段时间,我个人觉得还是用markdown编辑器效果更好,毕竟我个人更喜欢markdown。

于是,有了这个需求之后,立刻着手开始修改。

插件

一开始我自然是考虑使用掘金同款的ByteMD,毕竟,我最开始写文档的时候就是通过掘金。

当时就是因为掘金,才感觉markdown这种书写模式特别牛。

但是,ByteMD的pnpm安装一直不行,虽然用npm安装就正常了,但是这个问题搞得我很膈应,最终还是不了了之了。

除此之外,我查阅了现在市面上很多其他项目,现有的很多实现方式都偏向vue2,没有更好的方式。

截止2024年12月24日18点,我这边统计了一下市面上的三个较为热门的MD插件。

插件名 最后更新时间 是否中文文档 备注
v-md-editor 2023年9月 pnpm安装似乎有警报,但是不影响
ByteMD 2023年12月 × pnpm安装有问题
markdown-it 2024年3月 目前安装上没有什么太大问题

经过多次试错后,这三款markdown的插件,我最终选定了markdown-it

主要有群友推荐了markdown-it,其次在vue3中,另外两款插件并不好用,而且也没有他人的试错文档。

而markdown-it,我目前是完全把流程走通了。

使用

首先下载相关的工具库。

1
2
pnpm install highlight.js
pnpm install markdown-it

然后,我们需要先写个展示相关的组件,我个人命名为md-preview,然后全局使用。

目前尚未完成,等后续测试完毕,会更新该文档。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
<template>
<div class="markdown-body">
<div v-html="result"></div>
</div>
</template>

<script setup>
/* 安装流程
* pnpm install markdown-it
* pnpm install highlight.js
* pnpm install clipboard --save
* https://juejin.cn/post/6844904105970761741?searchId=20241219215618B6B10490F62417012BAF
*/

import MarkdownIt from 'markdown-it'
import Clipboard from 'clipboard'
import 'highlight.js/styles/github-dark.css'
import '@/assets/styles/github-markdown.scss'
import '@/assets/styles/github-markdown-dark.scss'
import '@/assets/styles/github-markdown-light.scss'

const props = defineProps({
value: {
type: String,
default: ''
}
})

const md = new MarkdownIt({
html: true,
linkify: true,
typographer: true,
highlight: function (str, lang) {
// 当前时间加随机数生成唯一的id标识
const codeIndex = parseInt(Date.now()) + Math.floor(Math.random() * 10000000)
// 复制功能主要使用的是 clipboard.js
let html = `<button class="copy-btn" type="button" data-clipboard-action="copy" data-clipboard-target="#copy${codeIndex}">复制</button>`
const linesLength = str.split(/\n/).length - 1
// 生成行号
let linesNum = '<span aria-hidden="true" class="line-numbers-rows">'
for (let index = 0; index < linesLength; index++) {
linesNum = linesNum + '<span></span>'
}
linesNum += '</span>'
if (lang && hljs.getLanguage(lang)) {
try {
// highlight.js 高亮代码
const preCode = hljs.highlight(lang, str, true).value
html = html + preCode
if (linesLength) {
html += '<b class="name">' + lang + '</b>'
}
// 将代码包裹在 textarea 中,由于防止textarea渲染出现问题,这里将 "<" 用 "&lt;" 代替,不影响复制功能
return `<pre class="hljs"><code>${html}</code>${linesNum}</pre><textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy${codeIndex}">${str.replace(
/<\/textarea>/g,
'&lt;/textarea>'
)}</textarea>`
} catch (error) {
console.log(error)
}
}

const preCode = md.utils.escapeHtml(str)
html = html + preCode
return `<pre class="hljs"><code>${html}</code>${linesNum}</pre><textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy${codeIndex}">${str.replace(
/<\/textarea>/g,
'&lt;/textarea>'
)}</textarea>`
}
})

const result = ref('')
result.value = md.render(props.value)

onMounted(() => {
const clipboard = new Clipboard('.copy-btn')

console.log(clipboard)
clipboard.on('success', function (e) {
e.clearSelection()
})
clipboard.on('error', function (e) {
console.error('Action:', e.action)
console.error('Trigger:', e.trigger)
})
})

watch(
props.value,
(val) => {
nextTick(() => {
if (val !== null && typeof val !== 'undefined' && val !== '') {
result.value = md.render(val)
}
})
},
{
immediate: true
}
)
</script>

<style lang="scss">
.markdown-body {
box-sizing: border-box;
min-width: 200px;
max-width: 980px;
margin: 0 auto;
padding: 45px;
}

@media (max-width: 767px) {
.markdown-body {
padding: 15px;
}
}

.hljs {
padding: 12px 2px 12px 40px !important;
border-radius: 5px !important;
position: relative;
font-size: 14px !important;
line-height: 22px !important;
overflow: hidden !important;

code {
display: block !important;
margin: 0 10px !important;
overflow-x: auto !important;

&::-webkit-scrollbar {
z-index: 11;
width: 6px;
}
&::-webkit-scrollbar:horizontal {
height: 6px;
}
&::-webkit-scrollbar-thumb {
border-radius: 5px;
width: 6px;
background: #666;
}
&::-webkit-scrollbar-corner,
&::-webkit-scrollbar-track {
background: #1e1e1e;
}
&::-webkit-scrollbar-track-piece {
background: #1e1e1e;
width: 6px;
}
}
.line-numbers-rows {
position: absolute;
pointer-events: none;
top: 12px;
bottom: 12px;
left: 0;
font-size: 100%;
width: 40px;
text-align: center;
letter-spacing: -1px;
border-right: 1px solid rgba(0, 0, 0, 0.35);
user-select: none;
counter-reset: linenumber;
span {
pointer-events: none;
display: block;
counter-increment: linenumber;
&:before {
content: counter(linenumber);
color: #999;
display: block;
text-align: center;
}
}
}
b.name {
position: absolute;
top: 2px;
right: 50px;
z-index: 10;
color: #999;
pointer-events: none;
}
}

.hljs {
position: relative;

.copy-btn {
position: absolute;
right: 16px;
top: 0;
z-index: 99;
color: #333;
padding: 4px;
cursor: pointer;
background-color: #fff;
border: 0;
border-radius: 2px;
}
}
</style>

样式

关于样式库,我没有特别挑,因为markdown-it可以自己摆弄样式库。

如果你有足够的时间,可以去看MarkDown在线样式,在这里慢慢的挑选你中意的样式。

不得不说,越简单往往越耐看,朴素简约的风格往往能更快的让人提炼信息。

结语

目前只是把展示的效果做好了,后续会考虑解决编辑器的问题。

参考

知乎:有哪些Markdown的CSS样式表推荐? - Yangg的回答

markdown-it代码块渲染、自定义行号、复制代码功能之前写过一篇关于代码块渲染添加自定义行号的文章:markdow - 掘金