最近在做前端项目,需要MD文件的录入及展示效果。
然后从录入组件到展示,出乎意料的麻烦。
倒不是这事儿实现的思路多难,而是实现过程中有很多出乎意料的岔道。
正文
本来一开始打算使用wangeEditor直接解决用户录入富文本的需求。
但是想了一段时间,我个人觉得还是用markdown编辑器效果更好,毕竟我个人更喜欢markdown。
于是,有了这个需求之后,立刻着手开始修改。
插件
一开始我自然是考虑使用掘金同款的ByteMD,毕竟,我最开始写文档的时候就是通过掘金。
当时就是因为掘金,才感觉markdown
这种书写模式特别牛。
但是,ByteMD的pnpm安装一直不行,虽然用npm安装就正常了,但是这个问题搞得我很膈应,最终还是不了了之了。
除此之外,我查阅了现在市面上很多其他项目,现有的很多实现方式都偏向vue2,没有更好的方式。
截止2024年12月24日18点,我这边统计了一下市面上的三个较为热门的MD插件。
经过多次试错后,这三款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渲染出现问题,这里将 "<" 用 "<" 代替,不影响复制功能 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, '</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, '</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 - 掘金