Hexo自定义主题(四)

标签: Hexo 分类: Javascript 创建时间:2019-10-12 01:42:43 更新时间:2025-01-17 10:39:22

这是hexo主题系列文章的第四篇,继续加油。

1.关于文章时间的问题

今天打开网站一看,发现了每篇文章的创建时间都有些问题,以前一直没有注意。

这里都写得是凌晨,我明明没有那么努力的。后来发现,在网站的_config.yml中,没有写时区,添加上timezone: Asia/Shanghai。

结果还是没解决问题,继续探究,后来我又查看了模板文件,原来是因为我显示时间的时候,用的是12小时而不是二十四小时,将模板文件中显示时间的地方由:moment(post.date).format(“YYYY-MM-DD hh:mm:ss”),改为:moment(post.date).format(“YYYY-MM-DD HH:mm:ss”)。

这下结果就对了。

2.使用hexo-renderer-marked渲染md字符串

先说下需求,主要是这样的。我通过hexo新建了一个html页面,将layout修改为自定义的内容,如果正常情况下,全部的html会经过hexo的渲染,生成html,但是我想如果页面中存在style和script标签,就将style提取到页面的头部,将script放到页面的底部,而不是被hexo的markdown直接将sytle和script放到了body里面,除了这两个内容,剩下的内容继续由markdown渲染器进行渲染。通过查看hexo-renderer-marked我发现它是调用了marked这么一个markdown渲染器,然后进行了相应的扩展,那这就好办了,因为安装hexo时已经安装了hexo-renderer-marked和mrked这两个库,直接用就好了。

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
var cheerio = require('cheerio');
// var marked=require("marked");
var hexomarked=require('./renderer');
var path=require("path");
// 文章渲染前,对其进行相关处理
// 定义渲染文件格式
var rendererFormat=".html,.md,.markdown,.mkd,.mkdn,.mdwn,.mdtxt,.mdtext";
hexo.extend.filter.register('before_post_render',function(data){
let content=data.content;
// 提取文章中的style和scripts分别将其放置到页面的head区域和body区域,而不是内容区域
// 提取完成之后,将剩下的内容继续进行渲染
if(data.layout!="post"){
data.scripts=[];
data.styles=[];
var $ = cheerio.load(content, {
ignoreWhitespace: false,
xmlMode: false,
lowerCaseTags: false,
decodeEntities: false
});
$('style').each(function(index,item){
var text=$(this).html().trim();
$(this).remove();
data.styles.push(text.replace(/[\r\n]/g,""));
})
$('script').each(function(index,item){
var text=$(this).html().trim();
$(this).remove();
data.scripts.push(text.replace(/[\r\n]/g,""));
})
// 获取内容中的非style和非script脚本部分
content=$.html();
//将剩下的内容进行markdown渲染
var extname=path.extname(data.path);
if(rendererFormat.indexOf(extname)>=0){
content=hexomarked(content);
}

data.content=content;
return data;
}
});

其中的hexomarked其实是hexo在marked的基础上实现的自定义的渲染方式,我只是把放到node_modules/hexo-renderer-marked/lib/renderer.js中的文件复制到了我主题下的script文件夹下,(不直接是使用hexo-renderer-marked的原因,也是因为我只想用renderer而不想用其他的代码)这样在生成文章的时候就会自动执行我写的代码了。

后来在hexo从3.x升级到4.x的过程中,出现了:Class constructor Renderer cannot be invoked without ‘new’ 的错误。

主要原因是4.x版本中,所有的类使用了es6的语法,所以apply就不可以使用了。

1
2
3
4
5
6
7
8
9
10
11
//旧版
function Renderer() {
MarkedRenderer.apply(this);

this._headingId = {};
}

//新版
class Renderer extends MarkedRenderer {

}

3.奇怪的标签页

(1) 我在文章中,写了两篇关于Avro的文章,我把标签修改为Avro

奇怪就奇怪在,运行 hexo g 后生成的tags/index.html中竟然出现了错误的html,明显html已经结束了,但是还是又出现了重复的html结束标签。

当我把这篇文章的这个tag标签,改为小写,结果就渲染成了正确的html。

(2) 仔细查看有些文章标签页总是少一个,比如我又三篇文章的tag是avro,但是显示出来的index页中却却缺少一项内容,而且使用的模板并不是我的tag.swig模板(通过修改模板查看得出结论)

(3) 临时解决方案:

1
2
## 新建tags页
hexo new page tags

将source/tags/index.md中添加:type: tags

同理新建categories页面

1
2
## 新建categories页
hexo new page categories

修改source/categories/index.md中添加:type: categories

5.代码调整生成tag标签的方式

需求是这样的,写文章很多,标签就很多,有时候同一个标签只是因为是大小写不同,就被分成了不同的标签,但是我想总会有写错的时候,就统一标签,将标签的第一个字母大写,这样就算是不小心写错了大小写,也会归为同一类标签下。通过参考hexo-generator-tag的源码方式,我发现了,通过在generator生成器中通过定义返回一个数组,就可以生成多个文件夹。代码如下:

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
var pagination = require('hexo-pagination');
var _=require("lodash")
//生成分类和标签时进行的处理
hexo.extend.generator.register('tag', function(locals){
let tagsPage={}
locals.posts.map(function(post){
//将文章的标签和分类首字母改为大写
var tags=post.tags.data;
for(let menuItem in tags){
let name=tags[menuItem].name;
// console.log(name.toLowerCase());
//将首字母改为大写
name=name.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase());
tags[menuItem].name=name;

//保存相关文章
if(!tagsPage[name]){
tagsPage[name]=[];
}
tagsPage[name].push(post);
}
});
let themeConfig=hexo.theme.config;
let perPage=(themeConfig.tag_generator&&themeConfig.tag_generator.per_page)?themeConfig.tag_generator.per_page:10;
var pages=[];
for(let tageName in tagsPage){
let page=tagsPage[tageName];
var data = pagination("/tags/"+tageName.replace(/\.|\s/g,"_"), page, {
perPage: perPage,
layout: ['tag', 'index'],
format: 'tags/%d/',
data: {
tag: tageName,
total:page.length
}
});
pages=[...pages,...data];
}
return pages;
});

生成分类的时候,就用:

1
hexo.extend.generator.register('category')

6.最终页面上的标签生成策略

生成标签云的思路就是,使用nodejs读取source/_posts目录下的所有md文件,使用gray-matter,解析文件标头,然后生成标签的数组,然后在页面上使用新的变量newTags,最后配合自定义生成标签的路由hexo.extend.generator.register(‘tag’)函数,最终形成了自己的标签云以及对应的路由。

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
var _uniqBy=require("lodash/uniqBy");
var fs=require("fs");
var path = require('path');
var grey_matter=require("gray-matter");
var _=require("lodash");
// 遍历文件夹
var rendererFormat=".html,.md,.markdown,.mkd,.mkdn,.mdwn,.mdtxt,.mdtext";
function travel(dir, callback) {
fs.readdirSync(dir).forEach(function (file) {
var pathname = path.join(dir, file);
if (fs.statSync(pathname).isDirectory()) {
travel(pathname, callback);
} else {
callback(pathname);
}
});
}
var postTags=[];
// console.log(hexo.source.base);
var source=hexo.source.base;
travel(path.resolve(__dirname, source+'/_posts'),function(filePath){
var extname=path.extname(filePath);
if(rendererFormat.indexOf(extname)>=0){
//读取文件,获取标签
try{
var data=fs.readFileSync(filePath,{
encoding: 'utf-8'
});
var obj=grey_matter(data);
var titleObj=obj.data;
var tags=titleObj.tags;
//console.log(tags);
if(tags){
//console.log(_.isArray(tags))
if(_.isArray(tags)){
postTags=_.merge(tags,postTags);
}else{
postTags.push(tags);
}
}
}catch(err){
console.log(err);
}
}
});
postTags=_.uniq(postTags);
postTags=_.map(postTags,function(item){
return _.capitalize(item);
});
hexo.extend.filter.register('template_locals', function(locals){
//生成页面顶部菜单栏
let menuArray=[];
//分类菜单
let categories=hexo.locals.get("categories").data;
let categoriesName=[];
for(let menuItem in categories){
let name=categories[menuItem].name;

// 如果已经存在相似的名称了,则不添加
//将首字母改为大写
name=name.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase());
if(categoriesName.indexOf(name)<0&&name!=null&&name!=""){
menuArray.push({"name":name,"path":"/categories/"+name.replace(/\.|\s/g,"_")+"/"});
}
}
menuArray=_uniqBy(menuArray,"name");
locals.headermenu=menuArray;

//修改标签,合并同类标签,
let tags=hexo.locals.get("tags").data;
// console.log(hexo);
let newTags=[];
for(let menuItem in tags){
let name=tags[menuItem].name;
//console.log(tags[menuItem]);
//将首字母改为大写
name=name.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase());
if(postTags.indexOf(name)>=0&&name!=null&&name!=""){
tags[menuItem].name=name;
tags[menuItem].path="/tags/"+name.replace(/\.|\s/g,"_")+"/";
newTags.push(tags[menuItem]);
}
}
locals.newTags=newTags;

return locals;
});

期间主要使用了lodash函数,nodejs的readFile函数,以及插件gray-matter。

7.生成文章的摘要

如果没有在more上面写东西的话,文章是没有摘要的,那么如何自动生成摘要呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var cheerio = require('cheerio');
hexo.extend.filter.register('after_post_render', function(data){

// 处理文章的摘要部分
if(!data.excerpt||data.excerpt==""){
var $ = cheerio.load(data.more, {
ignoreWhitespace: false,
xmlMode: false,
lowerCaseTags: false,
decodeEntities: false
});
var text=$("p").text();
data.excerpt=text.substring(0,250);
}

return data;
});

8.执行hexo clean 无法清理缓存

这个问题一直让我很头大,明明已经修改了after_post_render函数,但是就是应用不上去,比如上面的代码,data.excerpt这个,在页面上还是显示为空,即使你把data.content设置为空,还是渲染了以前的内容。这就比较尴尬了。我丧心病狂的都重启计算机了,才终于看到效果改变了。
其实如果hexo clean不起作用,就直接把hexo目录下的db.json直接删除就好了。这样就算删除了缓存了。

小额赞助
本人提供免费与付费咨询服务,感谢您的支持!赞助请发邮件通知,方便公布您的善意!
**光 3.01 元
Sun 3.00 元
bibichuan 3.00 元
微信公众号
广告位
诚心邀请广大金主爸爸洽谈合作
每日一省
isNaN 和 Number.isNaN 函数的区别?

1.函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

2.函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

每日二省
为什么0.1+0.2 ! == 0.3,如何让其相等?

一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 ===0.3。

每日三省
== 操作符的强制类型转换规则?

1.首先会判断两者类型是否**相同,**相同的话就比较两者的大小。

2.类型不相同的话,就会进行类型转换。

3.会先判断是否在对比 null 和 undefined,是的话就会返回 true。

4.判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number。

5.判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断。

6.判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断。

每日英语
Happiness is time precipitation, smile is the lonely sad.
幸福是年华的沉淀,微笑是寂寞的悲伤。