Loading...
墨滴
m

mingchiuli

2022/01/12  阅读:25  主题:极客黑

对mavon-editor进行定制

目的

之前在网上看到了一个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({
            highlightfunction(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, ignoreIllegalstrue}).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 {
  padding40px 2px 12px 40px !important;
  border-radius5px !important;
  position: relative;
  font-size14px !important;
  line-height22px !important;
  overflow: hidden !important;
  code {
    display: block !important;
    margin0 10px !important;
    overflow-x: auto !important;
    &::-webkit-scrollbar {
      z-index11;
      width6px;
    }
    &::-webkit-scrollbar:horizontal {
      height6px;
    }
    &::-webkit-scrollbar-thumb {
      border-radius5px;
      width6px;
      background#666;
    }
    &::-webkit-scrollbar-corner,
    &::-webkit-scrollbar-track {
      background#1e1e1e;
    }
    &::-webkit-scrollbar-track-piece {
      background#1e1e1e;
      width6px;
    }
  }
  // 行号样式
  .line-numbers-rows {
    position: absolute;
    pointer-events: none;
    top40px;
    bottom12px;
    left0;
    font-size100%;
    width40px;
    text-align: center;
    letter-spacing: -1px;
    border-right1px solid rgba(0000.66);
    user-select: none;
    counter-reset: linenumber;
    span {
      pointer-events: none;
      display: block;
      counter-increment: linenumber;
      &:before {
        contentcounter(linenumber);
        color#999;
        display: block;
        text-align: center;
      }
    }
  }
  // 代码语言
  b.name {
    position: absolute;
    top4px;
    right60px;
    z-index10;
    color#999;
    pointer-events: none;
  }
  // 复制按钮样式
  .copy-btn {
    position: absolute;
    top4px;
    right4px;
    z-index10;
    color#333;
    cursor: pointer;
    border0;
    border-radius2px;
    outline: none;
  }
}

.markdown-body pre.hljs {
  background#23241f;
}

// 漂亮的title
pre.hljs::after {
  height15px;
  width100px;
  margin-bottom: -7px;
  border-radius5px 5px 0 0;
  display: block;
  content"";
  backgroundurl(data:image/png;base64,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)
  10px center no-repeat;
  background-size: contain;
  position: absolute;
  top10px;
  left0;
  box-sizing: border-box;
  z-index1;
}

复制

复制部分用的是Clipboard,但是这个东西用起来还是有点小坑的。因为需要复制的形式不太好绑定一个单击事件,所以代码是这样的:

clipboard = new Clipboard(".copy-btn");
        this.$nextTick(() => {
          // 复制成功失败的提示
          clipboard.on("success", () => {
            this.$message.success("复制成功");
          });
          clipboard.on("error", () => {
            this.$message.error("复制失败");
          });
        });

把复制写到this. axios拿到数据以后了。但是有个问题,$axios是一个ajax请求,所以他给页面数据赋值的时候,页面到底初始化到什么程度是一个未知的事情。所以要用nextTick在下次DOM更新循环结束之后执行延迟回调,在修改数据之后立即使用这个方法,获取更新后的DOM。

另外就是要及时把Clipboard对象销毁掉,否则可能会出现点一下跳出来好几个成功弹窗这种情况。

m

mingchiuli

2022/01/12  阅读:25  主题:极客黑

作者介绍

m
mingchiuli