自从初次接触 Hexo 到现在已经有两年多的时间了,时间过得飞快啊,关于 Hexo 的优点不再赘述,关于个人站点的优点,有必要在强调一下,那就是极高的自由度,这也是这篇文章的基础。现在有时间刚好总结一下我对于 Hexo 做的一些自定义扩展,虽然之前可能在别的文章中或多或少的涉及了,但并没有统一整理过。
本人主题:Indigo,以下内容均基于此主题所写。
首先需要明白的是,Hexo 的博客内容(静态内容)均由 generate 生成,其核心是一个 node 应用,提供了一系列帮助函数,或者说调用接口;而各种主题,只不过是在其规定的框架内,以一种特定的模板(ejs/swg/pug),调用特定的 Hexo 帮助函数来完成的。在构造时,这些模板文件每次都会重新生成对应文件,例如文章页面,就是对应的模板文件将编译后的 markdown 格式的文本填入 HTML 页面,同时也会插入进去其他东西(比如题目,尾注等等)。以EJS+LESS为例:
由上面可知,对博客进行的任何修改,这里特指简单的、在已有界面上的修改,均需要找到渲染/生成该 HTML 页面(浏览器中我们可见的部分)的模板文件,在模板文件中进行我们想要的修改。如果涉及主题的CSS样式,则一般需要找到对应的 less 文件,如果里面用的变量代替,则还需要到存储变量值的文件里去修改变量的值,这样才完成样式的修改,但偷懒的方法是,直接在对应生成页面的模板文件中添加style代码段,然后用 !important 将你修改后的样式强制覆盖原先的主题样式。
总之,或许你的主题文件中包含很多的模板文件,但实际他们是有机的整体,都会在某个模板文件中被引用,从而组合成一个完整的整体,修改时要耐心的去找到最细粒度(对应html语句)的那部分。
主要有两种方式,一种是添加一个 md 文件,一种是直接放一个 html 文件,前者在渲染时会生成相应的 html 内容,两者本质上没有什么区别,取决于你添加的新页面的内容,比如一般的文本则使用 md 就可以了,但如果是复杂的 js插件,那还是直接添加 html 较好,方便修改。
这两种方式均需要将 md 文件或者 html 文件放入到 Hexo 根目录的 source 文件夹中,Hexo 的机制是 source 文件夹中的全部文件都会被原封不动的生成到 public 文件夹内(只有 md 文件会被“翻译”成 html 格式)。
这一部分可以在某些原有页面上添加,也可以是在新增页面上添加。主要是通过借助 Hexo 的程序接口,获取像 文章数量、分类数量、各种标签下分别有多少文章等等数据,通过这些数据,可以完成一些功能,比如:
主要基于的对象:分类(Category)、标签(Tags)、文章(Article)
可以扩展的操作:过滤、匹配、重构
其实扩展内容可以由阅读原先的主题文件的代码来理解,因为包括“归档”、“分类”等页面的显示,均用的 Hexo 的帮助函数,具体的函数接口定义与说明可以参照 Hexo的API说明。
扩展思路:这里使用 Tags 进行比对,相似越多的文章就越相关,在最后一步,Category 相同会优先入选,排序后只取最相关的 TOP3,不足三个则有多少显示多少;
扩展结构:
1. 找合适的文件插入实现上述功能的新函数,一般在主题的 plugin.js,之后以注册函数的形式完成代码实现
hexo.extend.helper.register('getTagsList', (archive, size) => {
var posts = hexo.locals.get('posts');
var articleArr = [];
var small = [];
size = size || 10;
var ishere = [];
for(var j = 0; j< archive.tags.length; j++){
var contnu = true;
var tname = archive.tags.data[j].name;
for(var p = 0; p< posts.length; p++){
var tid = posts.data[p]._id;
if(small.length == size || !contnu )
break;
for(var k = 0; k< posts.data[p].tags.length; k++){
if( tname === posts.data[p].tags.data[k].name
&& ishere.indexOf(tid) == -1
&& archive._id != tid){
if(posts.data[p].category === archive.category && archive._id != tid){
small.push(posts.data[p]);
contnu = false;
}
articleArr.push(posts.data[p]);
ishere.push(tid);
}
}
}
}
if(small.length >= 1){
return small;
//正常情况下返回文章数组
}else{
var ln = articleArr.length;
if ( ln < size)
return articleArr.slice(0, ln-1);
else
return articleArr.slice(ln-1-size, ln-1);
}
});
2. 新建子模板文件(为了模块分离)
<section>
<p><span>「 相关文章 」</span></p>
<ul>
<%
//页面全部内容,只有TOP3
var arr = getTagsList(page, 3);
//每次生成文章都会执行
for( var i in arr) {
var article = arr[i];
%>
<li>
<a href="<%- url_for(article.path) %>"><%- article.title%></a></b>
<!--显示文章的摘要-->
<p><%- truncate(strip_html(article.excerpt || article.content ), {length: 100}) %></p>
</li>
<%
}
%>
</ul>
</section>
```html
**3. 将上述新模板内容插入到原主题的对应位置内**
```html
<!--
这部分是整个文章页的汇总模板
-->
<%- partial('post/toc', { post: post}) %>
<article id="<%= post.layout %>-<%= post.slug %>"
class="post-article article-type-<%= post.layout %> fade" itemprop="blogPost">
<!--
post-card 即文章的主体部分,包括题目、分类、字数、及最下方的标签和分享图标等
-->
<div class="post-card">
<h1 class="post-card-title"><%- post.title %></h1>
<div class="post-meta">
<%- partial('post/date', {date_format: config.date_format}) %>
<%- partial('post/category') %>
<%- partial('post/wordcount') %>
<%- partial('plugins/page-visit') %>
</div>
<div class="post-content" id="post-content" itemprop="postContent">
<%- post.content %>
</div>
<%- partial('post/copyright') %>
<%- partial('post/reward-btn') %>
<div class="post-footer">
<%- partial('post/tag') %>
<%- partial('post/share-fab') %>
</div>
</div>
<!--
下面的模块,第一个是左右导航栏,第二个是咱们的相关文章模块,第三个是评论
最后,还包括了打赏图标(reward)在文章最底部
-->
<%- partial('post/nav') %>
<%- partial('post/postlist')%>
<%- partial('post/comment') %>
</article>
<%- partial('post/reward') %>
实现思路:统计分类、标签的重复个数,并绑定其所载文章的创建日期,这样整体上就有变量值、时间值两个维度,可以借助 D3.js 等做一些复杂的分析展示模块。
假象模块:
实现部分:与案例一类似,第二三步骤很简单,关键是上述假象模块的实现,主要包括 通过 Hexo API 得到相关数据 和 通过可视化库进行绘制。
19-12-20 新问题:引入资源文件
例如想在 Hexo 博客中的某个页面,做成一个资源分享页面(自己用或者给别人用),这样就会涉及文件的下载。其实有以下几种方式实现:
其实这个问题适用于博客中所涉及的全部资源文件,包括头像、文章中插入的照片等等。七牛云是个不错的对象存储平台(包括文档、图片、媒体文件等等),如果只是图片的话,推荐使用专职的图床进行存储(例如微博图床)。