想要找点什么呢?

幕冬有柒

Vue 中渲染markdown并实现代码高亮、行号、生成目录、代码块放大、复制代码等功能

幕冬有柒 · 2021-04-25 · 705 次阅读

Vue 中渲染markdown并实现代码高亮、行号、生成目录、代码块放大、复制代码等功能

安装依赖包

  • marked 渲染markdown语法转成HTML
  • lodash javascript原生库
  • clipboard 复制粘贴
  • highlight.js 代码的高亮
  • highlightjs-line-numbers.js 代码显示行号

marked和lodash和clipboard通过npm安装,highlight和highlightjs-line-numbers,script 引入即可

npm install marked -S
npm install lodash -S
npm install clipboard -S
<script src="https://image.bygit.cn/highlight.min.js"></script>
<script src="https://image.bygit.cn/highlightjs-line-numbers.min.js"></script>

代码

Template

<!-- 内容区域 -->
<div class="entry-content" id="content" v-html="compiledMarkdown"></div>
<!-- 目录 -->
<a-anchor
    class="toc"
    affix
    showInkInFixed
    :offsetTop="100"
    v-if="tocItems.length > 0"
>
    <!-- 递归组件 -->
    <AnchorLink :list="tocItems" />
</a-anchor>

AnchorLink递归组件

  <div>
    <a-anchor-link
      v-for="item in list"
      :key="item.anchor"
      :href="`#${item.anchor}`"
      :title="item.text"
    >
      <AnchorLink v-if="item.children" :list="item.children" />
    </a-anchor-link>
  </div>

Script

import AnchorLink from "../../components/AnchorLink";
import marked from "marked";
import { last } from "lodash";
const rendererMD = new marked.Renderer();
marked.setOptions({
  renderer: rendererMD,
});
export default {
  components: {
    AnchorLink,
  },
  data() {
    return {
      clipboard: '',
      anchors: [],
      tocItems: [],// 目录递归列表
      index: [],
    };
  },
  computed: {
    //此函数将markdown内容进一步的转换
    compiledMarkdown() {
      rendererMD.heading = (text, level) => {
        const anchor = this.add(text, level); //渲染目录的方法
        return `<h${level} id="${anchor}">${text}</h${level}>`;
      };
      return marked(content); //content 是markdown的内容
    },
  },
  mounted() {
    this.bindClick();
  },
  destroyed() {
    if (this.clipboard) this.clipboard.destroy();
  },
  methods: {
    //绑定Click 渲染代码高亮,行号
    bindClick() {
      this.$nextTick(() => {
        let content = document.getElementById("content");
        let pre = content.querySelectorAll("pre");
        for (let i = 0; i < pre.length; i++) {
          //这里应为是script引入的,所以直接调用hljs就行
          //代码块高亮
          hljs.highlightBlock(pre[i].querySelector("code"));
          //对代码块加行数
          hljs.lineNumbersBlock(pre[i].querySelector("code"));
          // 获取code去除标签,保留code里的内容 复制的时候用到
          let median = pre[i].querySelector("code").innerHTML.replace(/<\/?.+?>/g, "");
          let res = median.replace(/ /g, "");
          //添加3个html标签,分别是复制按钮,放大按钮,和一个textarea存放code里的内容
          let a = `<a class="copy-code" data-clipboard-action="copy" data-clipboard-target="#copy${i}"><i class="iconfont icon-fuzhi"></a>`;
          let b = `<a class="enlarge"><i class="iconfont icon-fangda"></a>`;
          let c = `<textarea style="position: absolute;top: -9999px;left: -9999px;z-index: -9999;" id="copy${i}">${res}</textarea>`;
          //追加标签
          pre[i].querySelector("code").insertAdjacentHTML("afterend", a);
          pre[i].querySelector("code").insertAdjacentHTML("afterend", b);
          pre[i].querySelector("code").insertAdjacentHTML("afterend", c);
          // 给放大按钮添加点击事件
          pre[i].querySelector(".enlarge").onclick = () => {
            if (pre[i].classList.contains("code-block-fullscreen")) {
              pre[i].classList.remove("code-block-fullscreen");
            } else {
              pre[i].classList.add("code-block-fullscreen");
            }
          };
        }
        //绑定复制按钮
        this.clipboard = new Clipboard(".copy-code");
        this.clipboard.on("success", (e) => {
          this.$message.success("复制成功");
        });
        this.clipboard.on("error", (e) => {
          this.$message.error("复制失败");
        });
      });
    },
    //渲染目录开始
    add(text, level) {
      const anchor = `toc${level}${++this.index}`;
      this.anchors.push(anchor);
      const item = { anchor, level, text };
      const items = this.tocItems;

      if (items.length === 0) {
        // 第一个 item 直接 push
        items.push(item);
      } else {
        let lastItem = last(items); // 最后一个 item

        if (item.level > lastItem.level) {
          // item 是 lastItem 的 children
          for (let i = lastItem.level + 1; i <= 6; i++) {
            const { children } = lastItem;
            if (!children) {
              // 如果 children 不存在
              lastItem.children = [item];
              break;
            }

            lastItem = last(children); // 重置 lastItem 为 children 的最后一个 item

            if (item.level <= lastItem.level) {
              // item level 小于或等于 lastItem level 都视为与 children 同级
              children.push(item);
              break;
            }
          }
        } else {
          // 置于最顶级
          items.push(item);
        }
      }
      return anchor;
    },
  },
}

  • 微信

Comments | 4 条评论

  • kuangruimin

    2021-07-27 | 2021-07-27

    Reply

    @kuang ruimin:


  • kuangruimin

    2021-07-27 | 2021-07-27

    Reply

    @kuangruimin666


  • kuangruimin

    2021-07-27 | 2021-07-27

    Reply

    777


  • xyz1041221997

    2021-06-23 | 2021-06-23

    Reply

    266