目的
之前在網上看到了一個mavon-editor魔改的版本,給代碼塊加上了行號和一個title,另外加了一個復制按鈕。當時由於種種原因並沒有去實踐。但是這樣天突然之間我又看到了那段代碼,於是自己就過去試了試,最后還是成功做好了。不過里面還是有些坑的。樣式是這樣:
這個樣式加上以后,我才發覺實際上mavon-editor會對樣式信息進行緩存,比如我在文章詳情加載過之后,到編輯的預覽界面也是一樣的樣式了。
思路
思路大概就是對mavon-editor使用的markdown-it流程進行定制,具體而言是把調用highlight.js的過程定制以下,首先建這樣一個js文件:
const hljs = require("highlight.js");
export const markdown = (mavonEditor ,content) => {
const md = mavonEditor.getMarkdownIt();
return md
.set({
highlight: function(str, lang) {
const codeIndex =
parseInt(Date.now()) + Math.floor(Math.random() * 10000000);
let html = `<button class="copy-btn" type="button" data-clipboard-action="copy" data-clipboard-target="#copy${codeIndex}">copy</button>`;
const linesLength = str.split(/\n/).length - 1;
console.log(linesLength)
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 {
const preCode = hljs.highlight(str, {language: lang, ignoreIllegals: true}).value;
html = html + preCode;
if (linesLength) {
html += '<b class="name">' + lang + "</b>";
}
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);
return
}
}
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>`;
},
}).render(content);
};
mavon-editor在工作的時候,有兩種類型。
-
一種是以v-model的形式,將markdown格式的內容直接雙向綁定給mavon-editor標簽。這種情況下,mavon-editor會調用其集成的markdown-it對markdown語法的內容進行解析,轉換成html的標簽,然后展示在其預覽界面上(用mavon-editor的話,展示文章也必然是用這個編輯器,將編輯模式默認關閉)。
-
二種是手動將markdown語法的內容轉換成html,然后再用v-html的形式和mavon-editor進行雙向綁定。這里采用的就是這種手段,只不過是手動的將代碼塊部分的處理進行了一定程度的定制,其他部分的代碼是沒有變化的(交給了mavonEditor.getMarkdownIt()去完成)。
注意好綁定方式以后就可以了。另外代碼塊的title是一個base64編碼的圖片。less的形式如下:
pre.hljs {
padding: 40px 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: 40px;
bottom: 12px;
left: 0;
font-size: 100%;
width: 40px;
text-align: center;
letter-spacing: -1px;
border-right: 1px solid rgba(0, 0, 0, 0.66);
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: 4px;
right: 60px;
z-index: 10;
color: #999;
pointer-events: none;
}
// 復制按鈕樣式
.copy-btn {
position: absolute;
top: 4px;
right: 4px;
z-index: 10;
color: #333;
cursor: pointer;
border: 0;
border-radius: 2px;
outline: none;
}
}
.markdown-body pre.hljs {
background: #23241f;
}
// 漂亮的title
pre.hljs::after {
height: 15px;
width: 100px;
margin-bottom: -7px;
border-radius: 5px 5px 0 0;
display: block;
content: "";
background: url(data:image/png;base64,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)
10px center no-repeat;
background-size: contain;
position: absolute;
top: 10px;
left: 0;
box-sizing: border-box;
z-index: 1;
}
復制
復制部分用的是Clipboard,但是這個東西用起來還是有點小坑的。因為需要復制的形式不太好綁定一個單擊事件,所以代碼是這樣的:
clipboard = new Clipboard(".copy-btn");
this.$nextTick(() => {
// 復制成功失敗的提示
clipboard.on("success", () => {
this.$message.success("復制成功");
});
clipboard.on("error", () => {
this.$message.error("復制失敗");
});
});
把復制寫到this.\(nextTick里的原因是,因為一般給頁面數據賦值,都是\)axios拿到數據以后了。但是有個問題,$axios是一個ajax請求,所以他給頁面數據賦值的時候,頁面到底初始化到什么程度是一個未知的事情。所以要用nextTick在下次DOM更新循環結束之后執行延遲回調,在修改數據之后立即使用這個方法,獲取更新后的DOM。
另外就是要及時把Clipboard對象銷毀掉,否則可能會出現點一下跳出來好幾個成功彈窗這種情況。