前言

参考:

实践贴

Hexo: 从零开始编写自己的主题

编写自己的 Hexo 主题 | Easy Hexo 👨‍💻

Hexo主题开发

理论贴

Hexo高级教程 - 文集 - 简书

hexo-pagination函数释义 | 竹山一叶

hexo 源码分析 | Weakyon Blog

hexo源码分析(二) | HDi - 随便记

Hexo源码剖析-CSDN博客

Hexo源码分析(一)issue #4976的研究 | 拔剑Sketon

基础知识

markdown→html

image-20241201094926215

我们的markdown源文件,通过转换插件转换为html格式内容。这部分内容结合特定的布局文件将会生成对应的html页面。文章和布局文件匹配的过程是由生成器完成的。

生成器(generator)

生成器实例

1
2
3
4
5
6
7
hexo.extend.generator.register('gen', function(locals){
return {
path: 'gen/index.html', // 根目录下生成gen/index.html文件
data: locals.posts, // 传入的数据是locals.posts
layout: ['gen'] // 布局使用名为gen的布局文件
}
});
1
2
3
4
5
6
7
8
9
10
// 替换原始的post生成器
hexo.extend.generator.register('post', function(locals){
return locals.posts.map(function(post){
return {
path: "book/"+post.path, // 生成文章的路径,这里的path是只读属性
data: post, // 文章内容
layout: 'post' // 对应布局,这里只处理布局为post的文章,会找对应的名为post的布局文件
};
});
});

post默认生成器

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
import type { PostGenerator, PostSchema, SiteLocals } from '../../types';
import type Document from 'warehouse/dist/document';

// https://github.com/hexojs/hexo/blob/master/lib/plugins/generator/post.ts
function postGenerator(locals: SiteLocals): PostGenerator[] {
const posts = locals.posts.sort('-date').toArray();
const { length } = posts;
// 获取所有文章(_posts目录下的所有文章)
return posts.map((post: Document<PostSchema>, i: number) => {
const { path, layout } = post;

// 如果文章没有layout属性,则跳过
if (!layout || layout === 'false') {
return {
path,
data: post.content
};
}

// 给文章添加上一篇和下一篇的链接
if (i) post.prev = posts[i - 1];
if (i < length - 1) post.next = posts[i + 1];

// 给文章添加layout属性,优先级:post > page > index
// 这里比较强盗了,如果文的布局不在layouts数组中,则直接添加到数组最前面
const layouts = ['post', 'page', 'index'];
if (layout !== 'post') layouts.unshift(layout);

// 给文章添加__post属性,方便在模板中判断
post.__post = true;

return {
path,
layout: layouts,
data: post
};
});
}

export = postGenerator;

综上,只要是_posts目录下的文章,且layout不为false,就会调用post生成器进行生成html。所以如果我们自定义自己的生成器,有些post页将会生成两次。

这种情况下就可以使用hexo-hide-posts插件对post生成器隐藏不需要的文章

hexo——优雅的隐藏文件

过程

page布局

butterfly的page汇总页默认显示banner图

但是每篇文章整一个图片有点困难。

模仿这位博主做无图汇总页面
迷失在黄风岭 - 静かな森

修改内容如下(及其劣质的模仿):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//- 这里是决定不同的菜单页面显示不同的布局
case page.type
when 'tags'
include includes/page/tags.pug
+commentLoad
when 'link'
include includes/page/flink.pug
+commentLoad
when 'categories'
include includes/page/categories.pug
+commentLoad
when '404'
include includes/page/404.pug
when 'shuoshuo'
include includes/page/shuoshuo.pug
//- 下面是自定义的布局内容
when 'code'
when 'diary'
when 'book'
when 'abstract'
include includes/page/hidden.pug //- 如果layout是自定义的布局类型,那么使用hidden.pug布局文件
default
include includes/page/default-page.pug
+commentLoad
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//- 隐藏页面布局(使用隐藏文章)
if theme.tag_ui == 'index'
include ../mixins/indexPostUI.pug
+indexPostUI
else
include ../mixins/innei.pug
#tag
//- .innei_article-sort-title= _p('page.diary')
- let hidden_posts = site.hidden_posts
- const hide_posts_config = config.hide_posts
- if (!hide_posts_config || !hide_posts_config.enable) {
- hidden_posts = site.posts
-}
//- - console.log(page.type)
- let sort_posts = getPostsByCategory(hidden_posts,page.type)
+inneiLayout(sort_posts)
include ../pagination.pug
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//- 模仿innei的手记布局,有些文章实际上不需要显示图片
//- 和butterfly主题对比,只是少了图片,羞耻
//- https://innei.in/timeline?type=post
mixin inneiLayout(posts)
.innei_article-sort
- let year
- posts.forEach(article => {
- const tempYear = date(article.date, 'YYYY')
- const noCoverClass = article.cover === false || !theme.cover.archives_enable ? 'no-article-cover' : ''
- const title = article.title || _p('no_title')
if tempYear !== year
- year = tempYear
.innei_article-sort-item.year= year
.innei_article-sort-item(class=noCoverClass)
.innei_article-sort-item-info
.innei_article-sort-item-time
i.far.fa-calendar-alt
time.post-meta-date-created(datetime=date_xml(article.date) title=_p('post.created') + ' ' + full_date(article.date))= date(article.date, 'MM/DD')
a.innei_article-sort-item-title(href=url_for(article.path) title=title)= title
- })
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

.innei_article-sort
margin-left: 10px
padding-left: 20px
border-left: 2px solid lighten($light-blue, 20)

&-title
position: relative
margin-left: 10px
padding-bottom: 10px
padding-left: 30px
font-size: 1.72em

&:hover
&:before
border-color: var(--pseudo-hover)

&:before
position: absolute
top: calc(((100% - 36px) / 2))
left: -9px
z-index: 1
width: w = 10px
height: h = w
border: .5 * w solid $light-blue
border-radius: w
background: var(--card-bg)
content: ''
line-height: h
transition: all .2s ease-in-out

&:after
position: absolute
bottom: 0
left: 0
z-index: 0
width: 2px
height: 1.5em
background: lighten($light-blue, 20)
content: ''

&-item
position: relative
display: flex
align-items: center
margin: 0 0 5px 10px
transition: all .2s ease-in-out

&:hover
&:before
border-color: var(--pseudo-hover)

&:before
$w = 6px
position: absolute
left: calc(-20px - 17px)
width: w = $w
height: h = w
border: .5 * w solid $light-blue
border-radius: w
background: var(--card-bg)
content: ''
transition: all .2s ease-in-out

&.no-article-cover
height: 80px

.article-sort-item-info
padding: 0

&.year
font-size: 1.20em
margin-bottom: 10px

&:hover
&:before
border-color: $light-blue

&:before
border-color: var(--pseudo-hover)

&-time
color: var(--card-meta)
font-size: 1.05em
float:left

time
padding-left: 6px
cursor: default

&-title
@extend .limit-more-line
color: var(--font-color)
font-size: 1.05em
transition: all .3s
-webkit-line-clamp: 2
float:left
padding-left: 20px

&:hover
color: $text-hover
text-decoration:underline;

&-img
overflow: hidden
width: 100px
height: 70px
addBorderRadius()

+maxWidth768()
width: 70px
height: 70px

:first-child
@extend .imgHover

&-info
flex: 1

新增post页布局

新增了隐藏文章book, diary, code, abstract四个布局,

如果隐藏文章没有定义布局或者定义的布局不在上述四个布局之内,

使用默认布局diary

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
/**
* Butterfly
* hidden generator
* 隐藏页的生成器逻辑
*/

'use strict'

hexo.extend.generator.register('hidden', function (locals) {
return locals.hidden_posts.map(function (post) {
const { path, layout } = post;
const layouts = ['book', 'diary', 'code', 'abstract'];
// 当前先这么写,不然上一页下一页用不了了
post.__post = true;

if (!layout || layout === 'false' || layouts.indexOf(layout) === -1) {
// 设置默认布局为diary
console.log("ERROR: No valid layout, use default layout [diary] for error post [%s]", post.title);
post.layout = 'diary';
}

return {
path,
data: post,
layout: layouts
};
});
});

布局文件直接 copy post.pug,后面慢慢改

日记布局修改