Nuxt3 美化markdown中的代码块

231 阅读2分钟

markdown 解析出的代码块(code 标签)是由pre标签包裹着的,可以通过 css 子代选择器去自定义代码块的背景色、字号等;除此之外,还可以给代码块添加复制按钮(最开始想通过伪元素+样式来实现,但是这种办法没法很好的监听点击事件),封装单独的复制按钮组件 A,再通过 createApp 语法将 A 挂载到 pre 标签。源码地址:代码可参考我的github

代码块样式

通过全局的 css 样式来实现,在pre > code 中增加预设的一些样式,如圆角,padding,背景色等。建议在 ContentDocContentRenderer 标签的父元素加一个 class 类名,避免污染 markdown 之外的元素样式;

/* post-body是父元素的类名 */
.post-body {
  pre:has(> code) {
    padding: 1.2em 1em;
    margin: 15px 10px;
    background-color: var(--post-box-bg);
    border-radius: 0.5em;
    font-size: 1em;
    line-height: 1.5em;
    overflow: auto;

    code {
      padding: 0;
      margin: 0;
      background-color: var(--post-box-bg); // 添加自己喜欢的颜色
      border-radius: 5px;
      font-size: 1em;
      line-height: 1.5em;
    }
  }
  :not(pre > code) {
    code {
      /* 行内代码样式 */
    }
  }
  blockquote {
    /* 引用文本块 */
  }
}

其他标签,如 li、h 标题等等,都可以用同样的方式来自定义样式

代码块复制功能

思路:通过查到所有的pre标签,给每一个pre标签动态挂载一个组件,用到了vue3 createApp API:第一个参数是根组件。第二个参数可选,它是要传递给根组件的 props,详细用法请见官网 Vue.js-createApp

复制按钮组件

dom元素主要实现复制按钮和已复制按钮的切换,需要用一个变量定义来控制;组件使用absolute绝对定位,放置在右上角(pre标签-relative);

<div class="copy-content">
  copySuccessed?复制成功的按钮:复制按钮
</div>  
<style>
.copy-btn {
  user-select: none;
  position: absolute;
  right: 0px;
  top: 0px;
  cursor: pointer;
  padding: 10px;
  border-radius: 0 0 0 var(--post-code-raius); // 全局css变量
}
</style>

script 标签中接收2个变量,codepre标签中的innerText,success 用于控制复制是否成功;

<script setup>
const props = defineProps({
  code: String,
  success: Boolean,
});
// 子组件不能直接改父组件传过来的props,所以需要用ref包装一下
const copySuccessed = ref(false);
copySuccessed.value = props.success;

const copyMessage = () => {
  // 去除多余的换行符
  const cleanedCode = props.code.replace(/\n\s*\n/g, '\n');
  navigator.clipboard
    .writeText(cleanedCode)
    .then(() => {
      copySuccessed.value = true;
      setTimeout(() => {
        copySuccessed.value = false;
      }, 2000);
    })
    .catch(err => {
      copySuccessed.value = false;
    });
};
</script>

挂载 复制组件 到pre标签

需要先找出页面所有的pre标签,再遍历所有的pre标签,将这段代码抽到一个hooks里,便于复用;

省略vue3的api的导入

export const useCodeCopy = () => {
  const postRef = ref();
  onMounted(() => {
    postRef.value = document.querySelectorAll('pre'); // 获取页面所有的pre标签
    nextTick(() => {
      addCopyCom();
    })
  })

  // 插入工具组件
  const addCopyCom = () => {
    postRef.value?.forEach((el: HTMLElement) => {
      if (el.classList.contains('code-copy-added')) return; // 已有类名,跳过
      // 第二个参数为props中的属性值
      const app = createApp(CodeTool, { code: el.innerText, success: false });
      const instance = app.mount(document.createElement('div'));
      el.classList.add('code-copy-added');
      el.appendChild(instance.$el);
    });
  };
}