Young Kbt blog Young Kbt blog
首页
  • java基础

    • Java基础
    • Java集合
    • Java反射
    • JavaJUC
    • JavaJVM
  • Java容器

    • JavaWeb
  • Java版本新特性

    • Java新特性
  • SQL 数据库

    • MySQL
    • Oracle
  • NoSQL 数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • ActiveMQ
    • RabbitMQ
    • RocketMQ
    • Kafka
  • 进阶服务

    • Nginx
  • Spring
  • Spring Boot
  • Spring Security
  • 设计模式
  • 算法
  • 知识
  • 管理

    • Maven
    • Git
  • 部署

    • Linux
    • Docker
    • Jenkins
    • Kubernetes
  • 进阶

    • TypeScript
  • 框架

    • React
    • Vue2
    • Vue3
  • 轮子工具
  • 项目工程
  • 友情链接
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 收藏
    • 关于
    • Vue2-Admin (opens new window)
    • Vue3-Admin(完善) (opens new window)
GitHub (opens new window)

Shp Liu

朝圣的使徒,正在走向编程的至高殿堂!
首页
  • java基础

    • Java基础
    • Java集合
    • Java反射
    • JavaJUC
    • JavaJVM
  • Java容器

    • JavaWeb
  • Java版本新特性

    • Java新特性
  • SQL 数据库

    • MySQL
    • Oracle
  • NoSQL 数据库

    • Redis
    • ElasticSearch
  • 数据库

    • MyBatis
    • MyBatis-Plus
  • 消息中间件

    • ActiveMQ
    • RabbitMQ
    • RocketMQ
    • Kafka
  • 进阶服务

    • Nginx
  • Spring
  • Spring Boot
  • Spring Security
  • 设计模式
  • 算法
  • 知识
  • 管理

    • Maven
    • Git
  • 部署

    • Linux
    • Docker
    • Jenkins
    • Kubernetes
  • 进阶

    • TypeScript
  • 框架

    • React
    • Vue2
    • Vue3
  • 轮子工具
  • 项目工程
  • 友情链接
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 收藏
    • 关于
    • Vue2-Admin (opens new window)
    • Vue3-Admin(完善) (opens new window)
GitHub (opens new window)
  • 关于 - 自我

  • 关于 - 本站

    • 本站 - 介绍
    • 本站 - 规划
    • 本站 - 搭建
    • 本站 - 主题
    • 本站 - 网站部署
    • 本站 - 服务器部署
    • 本站 - 评论模块
    • 本站 - 站点信息模块
    • 本站 - 自定义样式模块
    • 本站 - 记录曾阅读位置模块
    • 本站 - 私密文章模块
    • 本站 - 导航站模块
    • 本站 - 首页大图模块
    • 本站 - 代码块隐藏模块
      • 前言
      • 前提 1
      • 前提 2
      • 添加箭头图标
      • 添加Vue组件
      • 注意
      • 注册Vue组件
      • 结束语
    • 本站 - 全局时间提示模块
  • 关于 - 首页

  • 关于 - 技巧

  • 关于 - 随笔

  • 关于
  • 关于 - 本站
Young Kbt
2022-02-13
目录

本站 - 代码块隐藏模块原创

笔记

一个代码块的代码太多,会占据大量的篇幅,如果能选择性隐藏,页面也许更加好看。

2021-01-11 @Young Kbt

  • 前言
  • 前提 1
  • 前提 2
  • 添加箭头图标
  • 添加Vue组件
  • 注意
  • 注册Vue组件
  • 结束语

# 前言

目前适用版本是 Vdoing v1.x。

代码块可以隐藏,也可以展开,这和 ::: details 类似,下面是简单的代码块 Demo:

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello,World");
    }
}
1
2
3
4
5

看到代码块右边的箭头了吗,点击即可隐藏代码块,再次点击则会展开代码块。

本内容实现并不难,只需三步:

  • 添加箭头图标
  • 编写代码块模块的 Vue 组件
  • 全局注册 Vue 组件

实现内容:

  • 代码块的隐藏和显示

  • 美化代码块的 UI,趋向于 Mac

  • 优化代码块语言的显示,因为默认主题的一些语言如 stylus 是不会显示出来。本内容的优化无论代码块语言是什么(如 abc),都会显示出来,如下

    我的语言不是 Java、PHP、JS、SH,而是 abdedfg
    
    1

# 前提 1

本内容重新实现的一键复制功能是基于 vuepress-plugin-one-click-copy 插件(箭头左边),该插件已经内置 vuepress-theme-vdoing 主题,所以无需担心,如果你曾经卸载了该插件,则需要安装回来;如果已经安装,则无需看这一步:

yarn add vuepress-plugin-one-click-copy -D
1

当然,如果你懂得看下面的源码,则将适配 vuepress-plugin-one-click-copy 插件的代码进行修改,只需要提供其他插件的 class 名进行判断(Vue 组件的 108 - 119 行代码),并自行在 F12 调试,移动到满意的位置。

如果不知道自己是否曾卸载或存在该插件,则前往根目录下的 package.json 文件查看 devDependencies 是否有 vuepress-plugin-one-click-copy 插件。

# 前提 2

本功能需要代码块需要开启 行号 功能,该功能已经内置 VuePress,所以只需要开启该配置即可。

在 docs/.vuepress/config.ts 里开启行号:




 
 
 
 



export default defineConfig4CustomTheme({
    theme: "vdoing", // 使用 npm 包主题
    // ...
    markdown: {
        lineNumbers: true, // 显示代码块的行号
        extractHeaders: ["h2", "h3", "h4"], // 支持 h2、h3、h4 标题
    },
    // ...
});
1
2
3
4
5
6
7
8
9

# 添加箭头图标

图标库来自阿里云:https://www.iconfont.cn/。

如果你没有账号,或者觉得添加比较麻烦,就使用我的图标库地址,当你发现图标失效了,就请来这里获取新的地址,如果还没有更新,请在评论区留言。

当然,建议你使用自己的图标库,比较稳定。就像注册一个购物账户,然后添加到购物车即可。

在 docs/.vuepress/config.js(新版是 config.ts)的 head 模块里添加如下内容:

['link', { rel: 'stylesheet', href: '//at.alicdn.com/t/font_3114978_qe0b39no76.css' }]
1

# 添加Vue组件

在 docs/.vuepress/components 目录下创建 Vue 组件:BlockToggle.vue。如果不存在 components 目录,则请创建。

添加如下内容:

<template></template>

<script>
export default {
  mounted() {
    setTimeout(() => {
      this.addExpand(40);
    }, 1000);
  },
  watch: {
    $route(to, from) {
      if (to.path != from.path || this.$route.hash == "") {
        setTimeout(() => {
          this.addExpand(40);
        }, 1000);
      }
    },
  },
  methods: {
    // 隐藏代码块后,保留 40 的代码块高度
    addExpand(hiddenHeight = 40) {
      let modes = document.getElementsByClassName("line-numbers-mode");
      // 遍历出每一个代码块
      Array.from(modes).forEach((item) => {
        // 首先获取 expand 元素
        let expand = item.getElementsByClassName("expand")[0];
        // expand 元素不存在,则进入 if 创建
        if (!expand) {
          // 获取代码块原来的高度,进行备份
          let modeHeight = item.offsetHeight;
          // display:none 的代码块需要额外处理,图文卡片列表本质是代码块,所以排除掉
          if (
            modeHeight == 0 &&
            item.parentNode.className != "cardImgListContainer"
          ) {
            modeHeight = this.getHiddenElementHight(item);
          }
          // modeHeight 比主题多 12,所以减掉,并显示赋值,触发动画过渡效果
          modeHeight -= 12;
          item.style.height = modeHeight + "px";
          // 获取代码块的各个元素
          let pre = item.getElementsByTagName("pre")[0];
          let wrapper = item.getElementsByClassName("line-numbers-wrapper")[0];
          // 创建箭头元素
          const div = document.createElement("div");
          div.className = "expand icon-xiangxiajiantou iconfont";
          // 箭头点击事件
          div.onclick = () => {
            // 代码块已经被隐藏,则进入 if 循环,如果没有被隐藏,则进入 else 循环
            if (parseInt(item.style.height) == hiddenHeight) {
              div.className = "expand icon-xiangxiajiantou iconfont";
              item.style.height = modeHeight + "px";
              setTimeout(() => {
                pre.style.display = "block";
                wrapper.style.display = "block";
              }, 80);
            } else {
              div.className = "expand icon-xiangxiajiantou iconfont closed";
              item.style.height = hiddenHeight + "px";
              setTimeout(() => {
                pre.style.display = "none";
                wrapper.style.display = "none";
              }, 300);
            }
          };
          item.append(div);
          item.append(this.addCircle());
        }
        // 解决某些代码块的语言不显示在页面上
        this.getLanguage(item);
        // 移动一键复制图标到正确的位置
        let flag = false;
        let interval = setInterval(() => {
          flag = this.moveCopyBlock(item);
          if (flag) {
            clearInterval(interval);
          }
        }, 1000);
      });
    },
    getHiddenElementHight(hiddenElement) {
      let modeHeight;
      if (
        hiddenElement.parentNode.style.display == "none" ||
        hiddenElement.parentNode.className !=
          "theme-code-block theme-code-block__active"
      ) {
        hiddenElement.parentNode.style.display = "block";
        modeHeight = hiddenElement.offsetHeight;
        hiddenElement.parentNode.style.display = "none";
        // 清除 vuepress 自带的 deetails 多选代码块
        if (
          hiddenElement.parentNode.className == "theme-code-block" ||
          hiddenElement.parentNode.className == "cardListContainer"
        ) {
          hiddenElement.parentNode.style.display = "";
        }
      }
      return modeHeight;
    },
    // 添加三个圆圈
    addCircle() {
      let div = document.createElement("div");
      div.className = "circle";
      return div;
    },
    // 移动一键复制图标
    moveCopyBlock(element) {
      let copyElement = element.getElementsByClassName("code-copy")[0];
      if (copyElement && copyElement.parentNode != element) {
        copyElement.parentNode.parentNode.insertBefore(
          copyElement,
          copyElement.parentNode
        );
        return true;
      } else {
        return false;
      }
    },
    // 解决某些代码块的语言不显示在页面上
    getLanguage(element) {
      // 动态获取 before 的 content 属性
      let content = getComputedStyle(element, ":before").getPropertyValue(
        "content"
      );
      // "" 的长度是 2,不是 0,"x" 的长度是 3
      if (content.length == 2 || content == "" || content == "none") {
        let language = element.className.substring(
          "language".length + 1,
          element.className.indexOf(" ")
        );
        element.setAttribute("data-language", language);
      }
    },
  },
};
</script>

<style>
/* 代码块元素 */
.line-numbers-mode {
  overflow: hidden;
  transition: height 0.3s;
  margin-top: 0.85rem;
}
.line-numbers-mode::before {
  content: attr(data-language);
}
/* 箭头元素 */
.expand {
  width: 16px;
  height: 16px;
  cursor: pointer;
  position: absolute;
  z-index: 3;
  top: 0.8em;
  right: 0.5em;
  color: rgba(238, 255, 255, 0.8);
  font-weight: 900;
  transition: transform 0.3s;
}

/* 代码块内容 */
div[class*="language-"].line-numbers-mode pre {
  margin: 30px 0 0.85rem 0;
}
/* 代码块的行数 */
div[class*="language-"].line-numbers-mode .line-numbers-wrapper,
.highlight-lines {
  margin-top: 30px;
}
/* 箭头关闭后旋转 -90 度 */
.closed {
  transform: rotate(90deg) translateY(-3px);
  transition: all 0.3s;
}
li .closed {
  transform: rotate(90deg) translate(5px, -8px);
}
/* 代码块的语言 */
div[class*="language-"]::before {
  position: absolute;
  z-index: 3;
  top: 0.3em;
  left: 4.7rem;
  font-size: 1.15em;
  color: rgba(238, 255, 255, 0.8);
  text-transform: uppercase;
  font-weight: bold;
  width: fit-content;
}
/* li 下的代码块的语言和 li 下的箭头 */
li div[class*="language-"]::before,
li .expand {
  margin-top: -4px;
}
/* 代码块行数的线条 */
div[class*="language-"].line-numbers-mode::after {
  margin-top: 35px;
}
/* 代码块的三个圆圈颜色 */
.circle {
  position: absolute;
  top: 0.8em;
  left: 0.9rem;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #fc625d;
  -webkit-box-shadow: 20px 0 #fdbc40, 40px 0 #35cd4b;
  box-shadow: 20px 0 #fdbc40, 40px 0 #35cd4b;
}
/* 代码块一键复制图标 */
.code-copy {
  position: absolute;
  top: 0.8rem;
  right: 2rem;
  fill: rgba(238, 255, 255, 0.8);
  opacity: 1;
}
.code-copy svg {
  margin: 0;
}

/* 如果你浅色模式的代码块背景色是浅灰色,则取消下面的注释使代码生效,如果是黑色,则注释下面的三段代码(我注释了,因为是黑色背景) */
/* .theme-mode-light .expand {
  color: #666;
}
.theme-mode-light div[class*="language-"]::before {
  color: #666;
}
.theme-mode-light .code-copy {
  fill: #666;
} */
</style>
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235

第 7 行和第 14 行的参数 40 是隐藏代码块后,保留的代码块高度,40 是默认值。

注意

  • 如果浅色模式的代码块背景色是浅灰色,则取消 226 - 234 的注释使代码生效(模板已经取消注释)
  • 如果是黑色,则注释 226 - 234 的代码(我自己的注释了,因为我的代码块是黑色背景)
  • 如果不喜欢代码块的语言变成大写,则注释 188 行的 text-transform: uppercase;

如果你想要你的代码块和我一样是 黑色,则打开 docs/.vuepress/styles/palette.styl 文件,替换掉原来的浅色模式:

.theme-mode-light
  --bodyBg: #f4f4f4
  --mainBg: rgba(255,255,255,1)
  --sidebarBg: rgba(255,255,255,.8)
  --blurBg: rgba(255,255,255,.9)
  --customBlockBg: rgba(255,255,255,.9)
  --textColor: #00323c
  --textLightenColor: #0085AD
  --borderColor: rgba(0,0,0,.15)
  // 代码块浅色主题
  //--codeBg: #f6f8fa
  //--codeColor: #24292e
  //codeThemeLight()
  // 行高亮颜色,和代码块浅色主题一起使用,一起注释
  //div[class*="language-"]
  //  .highlight-lines
  //    .highlighted
  //      background-color rgba(200,200,200,.4)
  //  &.line-numbers-mode
  //    .highlight-lines .highlighted
  //      &:before
  //        background-color rgba(200,200,200,.4)
  // 代码块深色主题
  --codeBg: #282C34
  --codeColor: #D4D4D4
  codeThemeDark()
  // 行高亮颜色,和代码块深色主题一起使用,一起注释
  div[class*="language-"]
    .highlight-lines
      .highlighted
        background-color rgba(0,0,0,.66)
    &.line-numbers-mode
      .highlight-lines .highlighted
        &:before
          background-color rgba(0,0,0,.66)
  div[class*="language-"].line-numbers-mode::after  // 代码块的行数和内容分割线颜色
    border-right 1px solid rgba(0, 0, 0, 0.66)
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

如果你喜欢加粗的 绿色、`` 包裹的 英文高亮 abcd、<mark></mark> 包裹的 文字高亮、深色模式的颜色(点击右下角的衣服图标,切换深色模式)等等,那么可以参考我的自定义样式模块,左侧的关于本站目录下就能找到。

# 注意

  • vuepress-plugin-one-click-copy 插件在移动端(手机端)失效,因为其自带的隐藏效果原因,这并不是本模块引起,而是本身插件的设计问题,所以如果觉得移动端也想要支持一键复制,请更换其他插件,并自行修改源码进行适配
  • 低分辨率的电脑,会导致代码的行数与代码不对应(代码行数溢出),这并非本模块原因,而是 VuePress 代码块本身的原因,可能新版本会修复

# 注册Vue组件

在 docs/.vuepress/config.js(新版是 config.ts)的 plugins 中添加插件配置。

添加如下内容:

    module.exports = {
        plugins: [
            {
                name: 'custom-plugins',
                globalUIComponents: ["BlockToggle"] // 2.x 版本 globalUIComponents 改名为 clientAppRootComponentFiles
            }
        ],
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    import { UserPlugins } from 'vuepress/config'
    plugins: <UserPlugins>[
        [
        	{
            	name: 'custom-plugins',
            	globalUIComponents: ["BlockToggle"] // 2.x 版本 globalUIComponents 改名为 clientAppRootComponentFiles
        	}
        ]
    ]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    # 结束语

    如果你正在热编译 markdown 的代码块,它不会立马生效,你只需要刷新下就能看到效果,而打包后,效果是会生效,无需担心。

    如果你还有疑惑,可以去我的 GitHub 仓库或者 Gitee 仓库查看源码。

    • GitHub (opens new window)

    • Gitee (opens new window)

    如果你有更好的方式,评论区留言告诉我,或者加入 Vdoing 主题的 QQ 群:694387113。谢谢!

    编辑此页 (opens new window)
    #本站
    更新时间: 2023/09/18, 16:34:13
    本站 - 首页大图模块
    本站 - 全局时间提示模块

    ← 本站 - 首页大图模块 本站 - 全局时间提示模块→

    最近更新
    01
    技术随笔 - Element Plus 修改包名 原创
    11-02
    02
    Reactor - 扩展性
    11-02
    03
    Reactor - 最佳实践
    11-02
    更多文章>
    Theme by Vdoing | Copyright © 2021-2024 Young Kbt | blog
    桂ICP备2021009994号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式