前言

  我的博客主题采用的是black-blue,从SPFK和yalia主题摇身一变过来的,整体风格偏黑,我比较喜欢,最重要的是,手机版有目录页面!!!
  然而最近在使用的过程中,发现代码块的效果有很多问题

  • 代码块的标题栏长度,随着滚动会不及代码的长度
  • 代码块标题栏没有当前语言的显示
  • 代码块不支持VEX等大部分的CG语言

操作截图

代码块滚动问题

  滚动条的问题原本不难解决,只要让它窗口监听resize事件,当窗口发生变化的时候,让代码的长度匹配标题的长度。
  然而这里并没有这么简单,最主要的问题是,标题栏居然是个CSS伪元素,正如上面截图看到的,它的element是::before,那么要对其进行DOM操作就变得非常曲折。
  后面我上网查了之后,得出结论就是要动态加入style标签修改::before的样式。
  虽然思路是对的,但是代码块的::before都是源于同一个CSS,直接修改就会修改全部的代码块,最后只能让一个代码块适配。
  因此我要为一个代码块另外创建一个有区分的CSS样式表。
  这个过程我一度怀疑这样的可行性,如此麻烦还不如用div进行操作。
  后来还是让我找到了解决方案,将下面的代码放到article.ejs里面(这个文件在主题文件夹的layout里面)
  这样在生成文章html的时候,代码就会嵌套到文章当中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//添加到article.ejs中
function codeAutoResize(){
if($(".highlight").length >   0) {//判断代码块是否存在
$(".highlight").each(function(index){//遍历代码块
let parentWidth = $(this).css('width');//获取代码块的宽度
let childWidth = $(this).children('table').css('width');//获取代码的宽度
if(parseInt(parentWidth) < parseInt(childWidth) ){//转成整数比较宽度大小
$(this).children('style').remove();//清理style标签(不会让style标签无限增加)
$(this).addClass("resize"+ index);//添加class名 用于区分不同的代码块
$(this).append("<style>.highlight.resize"+ index +"::before{width:" + childWidth + "}</style>");//根据class名来修改宽度
}else{
$(this).children('style').remove();
$(this).addClass("resize"+ index);
$(this).append("<style>.highlight.resize"+ index +"::before{width:" + parentWidth + "}</style>");
}
});
}
}

codeAutoResize();//初始化

$(window).resize(function(){
codeAutoResize();//改变尺寸的时候执行
});

代码块标题栏没有当前语言的显示

  在上面的截图可以看到伪元素的content上是有attr(data-lang)属性。
  网上查了才知道原来这个可以获取html的自定义属性嵌入到内容中,显然这里的html就没有这个属性。
  但是代码块是在哪里生成的,又应该在哪里嵌入这个html属性呢?
  经过我一番摸索,在article.ejs里面发现了猫腻,

操作截图

  上面的div标签是通过浏览器的控制台发现的,但是文章内容这么多,为何ejs的模板却这么短。
  原来所有的内容都在post.content里面了。
  也就是说hexo内部完成了markdown的解析,作为hexo的使用者只需要关心网站的架构即可。
  但是这样就导致我没有办法给相应的代码块添加对应语言的标签了。(或许可以通过jquery添加对应的data-lang属性,但是获取到对的应语言似乎挺麻烦的)

  鉴于此,我就在hexo里面寻找答案,况且后面解决代码高亮的问题也许好在hexo的内核里面寻求答案。
  经过很长时间的摸索,我终于找到了关键代码。在node_module中的hexo\lib\plugins\filter\before_post_render文件其中有backtick_code_block.js的js文件。
  这个文件就是hexo解析markdown代码块的核心代码,通过在这里加入console.log之后可以在hexo生成文章链接的时候看到信息反馈在git中,

操作截图

  这里通过正则表达式将代码块识别出来,并且分别截获代码块后面的内容,进行不同的操作。而关键的代码语言存储到options.lang中,最后options传入到highlight函数中。
  在node_module的hexo-util\lib中可以找到highlight.js,这里就可以找到html代码块的生成代码了。

操作截图

  在这里加入data-lang就可以在生成代码块的时候将语言的信息嵌套到html供::before去调用了。

加入自定义语言高亮支持

  通过上面的一通操作可以知道,hexo解析markdown代码块是通过highlight.js来实现的。上面hexo的highlight.js是hexo内部用来调用外部的highlight.js用的,在node_module文件夹中就有highlight.js,专门用来高亮代码的脚本。
  奈何特效相关的编程语言比较小众,它居然还支持MEL,我都惊了。然而VEX还是个遗憾,另外之前我写了正则表达式相关的博文,正则表达式也没有高亮支持。

  既然没有那就做一个呗,反正它内部有那么多门语言作为参考我就不信自己搞不定,所以我先从简单的正则表达式入手。
  不过加入之前要先在hexo弄好相关的代码,否则就会跳过脚本的。
  首先是node_modules\highlight.js\lib 的 index.js,需要在里面添加新的注册语言。
  另外node_modules\hexo-util 的 highlight_alias.json可以添加语言的模糊识别。
  后面就是如何对相应的内容进行高亮显示。

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
module.exports = function(hljs) {
var COMMA = {
className: 'keyword',//识别成功之后加入的class名
begin: '[,]'//正则表达式识别
};

var Key = {
className: 'keyword',
begin: '[?!$*+.\^|]'//识别正则表达式的典型符号
};

var BUILD_IN = {
className: 'class',
begin: /\\/ ,
end: '[dfnrsStvbBw]'//识别正则表达式\n \w 等标志
};

var PARENTHESIS = {
className: 'value',
begin: /[()]/,
};

return {
case_insensitive: true,
contains: [
{
className: 'strings',
begin: /\{/, end: /\}/,
relevance: 0,
contains: [
hljs.C_NUMBER_MODE,
COMMA,
]
},
hljs.C_LINE_COMMENT_MODE,//C语言单行注释
hljs.C_BLOCK_COMMENT_MODE,//C语言多行注释
hljs.C_NUMBER_MODE,//识别数字
Key,
BUILD_IN,
PARENTHESIS

]
};
};

  从上面的代码可以知道,highlight.js是通过正则表达式找到相应的代码加入class名,而着色是需要自己写css给相应的代码进行着色。

1
2
3
(<script>(.*\n)+</.*?>)|(<.*?>)(?=(.*\n)+
```[a-zA-Z])|(<.*?>)(?!(.*\n)+
```)

操作截图


  貌似正则表达式还是比较简单的,要处理Vex这种复杂的语言要如何进行正则表达式判断呢?
  其实没有那么复杂,因为VScode和sublime都支持VEX高亮,截取里面的关键代码就好了。
  VEX高亮脚本-链接

  当然别忘了加入CSS样式处理高亮效果

1
2
3
4
5
6
7
8
9
10
pre .vex_built_in{color:#66cccc}
pre .vex_type{color:#ed77ed}
pre .vex_operator{color:#ed77ed}
pre .vex_string{color:#63c563}
/* 这个部分Houdini没有高亮 有需要的话可以自己设置 */
/* pre .vex_context{color:#ed77ed} */
/* pre .vex_variable{color:#eecd10} */
/* pre .vex_function{color:#66d9ef} */
/* pre .vex_title{color:#66d9ef} */
/* pre .vex_number{color:#7163d7} */

  Vex代码的呈现效果:

1
2
3
4
5
6
7
for (int i = 0; i < @numprim; i++)
{
float test = 1;
string group_name = "curve" + itoa(i+1);
setprimgroup(0, group_name, i, true);
}

操作截图