Hexo高级教程之主题开发
原文链接 https://jamling.github.io/2016/07/14/Web-Hexo-dev-theme/
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。
引言
有感于hexo高级教程实在太少,当初本人在开发Nova主题时,曾遇到过不少坑,为填这些坑,较为深入地学习了hexo源码,又自学了不少node.js知识,才总算将这些坑基本填完。本着人人为我,我为人人的分享精神,特开一hexo高级教程专题,希望广大hexo爱好者拍砖~
本系列的定位为高级教程,所以要求读者具备以下知识或技能:
- 前端技术:前端基础知识不用说了,必须要具备的比如HTML,CSS,Javascript,Node.js。如果知识储备不足,推荐去W3C School好好学习。
- hexo模板:hexo中的layout模板都是使用某个具体的模板引擎写的,模板引擎有
swig
,ejs
,jade
等。layout可以视为MVC模式中的View
层,用于负责具体页面的展示。 - hexo变量:hexo内置了不少常用的变量,例如site.posts是站点所有的博客文章, config为hexo博客设置,page为hexo页面对象。hexo变量可以视为MVC模式中的
Model
层,负责给View
提供要展示的数据。 - [辅助函数]:hexo中内置了不少[辅助函数],这些辅助函数可以在模板中直接使用,用于快速地插入要展现的变量内容。辅助函数与MVC中的Controller有点类似,负责数据
Model
的获取以及如何在View
中展示。 - Hexo基础知识:基础知识可以自行度娘或谷歌。PS:个人建议还是看官方文档,有简体中文版本,遗憾的是,官方网站在国内访问有点慢☺。
主题修改
在讲到主题开发之前,不得不讲一下主题修改。目前hexo已有许多成熟的主题。但是未必完全符合博主的要求。灵活性好一些的主题,可能通过修改主题配置可以达到博主的目的,有些则需要修改主题模板或CSS甚至是辅助函数。不过与开发全新的主题相比,工作量还是少了许多。个人建议,如无必要,没有必要开发全新的主题。毕竟博客网站重的是内容,而不是外观。大多数主题,都具备了博客该有的功能,就不必像我如此折腾。当然做为极客的人们则另当别论。
主题配置修改
这部分相对简单,因为主题一般有相关的文档来告诉你如何修改。
以主题Nova为例,Nova主题在菜单配置上,有项导航菜单叫做捐赠墙,捐赠墙是http://www.ieclipse.cn 特有的模块,对于其它博客站点并不适用,那么,只需要将它删除或使用#将其注释即可。这样,它就不会出现在菜单栏中了。
主题风格修改
个人推荐在已有主题样式的基础上,新建一个新的CSS文件,并做为引入样式的最后一个。因为CSS按加载的顺序,如果发现有相同选择器的样式,则后面的CSS规则会合并或覆盖原有的规则。举个例子,原来主题中的链接(a标签)颜色为蓝色(#00f
),可以重写链接(a标签)的CSS。
原来的css:
a {
color: #00f;
}
追加的css:
a {
color: #f96;
text-decoration: none;
}
color规则会覆盖原来的color规则,而text-decoration则会作为新规则引入。CSS查看器,基本上浏览器都自带此功能。调试相对来说比较简单。
主题模板修改
在此,还是以Nova主题为例,如果站点不考虑国际化,只做单语言站点,则没有必要保留语言选择功能。遗憾的是,想要不显示,则不能通过修改主题配置来实现,需要修改主题的模板文件。Nova主题的导航栏菜单位于layout/partial/header.swig
中,使用记事本之类的编辑打开它,将
<ul class="nav navbar-nav navbar-right">
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{__('page.language')}} <span class="caret"></span></a>
<ul class="dropdown-menu">
{%- for lang in get_langs() %}
<li><a href="{{switch_lang(lang)}}">{{ lang_name(lang) }}</a></li>
{%- endfor %}
</ul>
</li>
</ul>
这一段html删除或注释即可
辅助函数修改
以官方的辅助函数list_archives为例,虽然此函数可以设置class参数,不过它的内部在生成ul和li时,都使用了动态的class,自动给class加了后缀。如下所示:
if (style === 'list') {
result += '<ul class="' + className + '-list">';
for (i = 0, len = data.length; i < len; i++) {
item = data[i];
result += '<li class="' + className + '-list-item">';
result += '<a class="' + className + '-list-link" href="' + link(item) + '">';
这样css中必须使用.xxx-list作为ul,.xxx-list-item为作li的样式,本着精减html的原则,修改后的代码为:
if (style === 'list'){
result += '<div class="' + className + '">';
for (i = 0, len = data.length; i < len; i++){
item = data[i];
result += '<a class="' + className + '-item" href="' + link(item) + '">';
result += transform ? transform(item.name) : item.name;
使用div和a来简化布局。
主题开发
有了前面的主题修改经验,相信博主们对hexo主题已经有一定的了解了。在这里,我把主题开发分为两种
主题迁移,除了hexo之外,还有许多其它的优秀博客系统,比如Wordpress,它们也有自己的主题。其中不乏一些优秀的主题。hexo中有不少主题就是迁移自其它博客系统的优秀主题。此种方式,可以最大方式的利用成熟主题的布局和样式甚至模板。比如,主页博客文章列表,原有的主题可能是将数据库查询结果集遍历输出为html,而迁移之后的主题,则需要对site.posts遍历并输出为html。
全新开发,全新开发是本文介绍的重点,但是个人并不推荐,除非具备一定的设计能力,它需要从零开始对博客进行设计,比如排版,布局,功能等等。本人开发Nova主题,主要是因为目前的主题+插件,不能解决我github项目文档页的展示问题,其次,也为能够更好更深入地学习前端技术☺。
主题设计
以Nova为例,我将博客站点分为3模块
博客文章 与其它主题的博客文章一样,博客文章有:首页、标签、分类、归档、分页等基本功能模块。 在版面上,它是一个2栏布局,主栏显示文章列表或文章详情,侧边栏用于放置窗口小部件或者文章目录。
单页 普通单页也采用主-侧边栏布局,主栏显示文章详情,侧边栏显示文章目录。 对于特别的单页,则使用单独的layout。
项目 项目模块作为Nova主题一大特色,采用三栏布局方式,左侧边栏显示项目导航,主栏显示项目文档内容,右侧边栏则放置文档目录。为处理项目相关的页面,Nova引入一个名为
project
的layout。
主题模板
在使用主题模板之前,先确定一种模板引擎,Nova主题使用的是swig
模板,这也是hexo默认的渲染模板。
所有的主题模板文件须放在主题layout
目录下,其中index模板与layout模板必不可少。不然运行会报错。在layout模板,可以将html主体结构写入其中。
<!DOCTYPE html>
<html lang="{{ page.lang }}">
<head>
<meta charset="utf-8">
<title>{{ head_title() }}</title>
<!--设置浏览器兼容模式 -->
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<!--支持响应式 -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 网站关键字,影响SEO -->
<meta name="keywords" content="{{ head_keywords() }}">
<!--网站描述,影响SEO -->
<meta name="description" content="{{ head_description() }}">
<!-- Canonical links -->
<link rel="canonical" href="{{ url }}">
<!--加载全局js或css -->
{{ head_jscss() }}
<!-- RSS -->
{{ feed_tag('atom.xml') }}
</head>
<!--Html主体 -->
<body>
<!-- header -->
{{ partial('partial/header') }}
<!-- main -->
{{ body }}
<!-- footer -->
{{ partial('partial/footer') }}
<!-- fixed action bar -->
{{ partial('partial/fab') }}
<!-- after footer, 第三方脚本放在最后,以免影响网页内容加载 -->
{{ js('js/script.js')}}
{{ partial('partial/baidu_analytics') }}
{{ partial('partial/jiathis_share') }}
</body>
</html>
index模板作为博客首页的渲染页,其实也是属于post模板的一种。除了layout模板外,我将其它的模板都做了归类,跟博客文章相关的,都在post子目录中;跟单页相关的放置在page子目录中;跟项目相关的,都放置在project子目录中。详细介绍,请访问nova layout
下面详细介绍博客文章中的首页和详情页模板
文章首页
首页即文章列表页,主栏主要显示文章列表。文章列表项显示标题,日期,分类,标签,文章摘要等信息及分享,评论等操作项,核心代码如下:
<main>
{%- for post in page.posts %}
<div class="card hoverable">
<div class="card-content">
<h3 class="card-title">
<a href="{{ url_for_lang(post.path) }}" class="article-title">{{ post.title }}</a>
</h3>
<div class="divider"></div>
<div class="section post-header">
<!-- sub element must be span -->
<span class="icon nova-calendar">{{ time_tag(post.date) }}</span>
{{ post_cates(post) }} {{ post_tags(post) }}
</div>
<div class="excerpt">{{ page_excerpt(post) }}</div>
</div>
<div class="divider"></div>
<div class="card-action">
<!-- 评论,分享,阅读全文等链接 -->
</div>
</div>
{%- endfor %}
<nav>{{ nova_paginator({total:page.total, class:'pagination'}) }}</nav>
</main>
输出结果预览: {% raw %} {%- for post in page.posts %} {{ post.title }} <!-- sub element must be span --> {{ time_tag(post.date) }} {{ post_cates(post) }} {{ post_tags(post) }} {{ page_excerpt(post) }} {{('sns.share')}} 0 {{('sns.comment')}} {{('sns.like')}} {{('page.more')}} {%- endfor %} {{ nova_paginator({total:page.total, class:'pagination'}) }} {% endraw %} 侧边栏,侧边栏主要由窗口小部件组成。如文章分类
<div class="panel panel-primary" id="category">
<div class="panel-heading">
<h3 class="panel-title">{{ _p('category.name') }}</h3>
</div>
<!-- use category tree -->
{{ nova_list_categories(site.categories, {class:'list-group', depth: 10, children_indicator: 'category'}) }}
</div>
页面预览: {% raw %} {{ __('category.name') }} <!-- use category tree --> {{ nova_list_categories(site.categories, {class:'list-group', depth: 10, children_indicator: 'category'}) }}
{% endraw %}
文章详情页
文章详情页,主栏显示文章内容、评论、上一页和下一页导航。
<article class="article post" itemscope itemtype="http://schema.org/Article">
<header class="article-header">
<div class="page-path"><span class="post-category">{{ page_path(post)}}</span></div>
<div class="divider"></div>
{%- if is_post() %}
<h1 class="article-title" itemprop="name">{{ post.title }}</h1>
{%- else %}
<h1>
<a href="{{ url_for_lang(post.path) }}" class="article-title" itemprop="name">{{ post.title }}</a>
</h1>
{%- endif %}
<div class="post-header">
<span class="icon nova-calendar"><span class="hidden-xs">{{__('page.written_on')}}</span>{{ time_tag(post.date) }}</span>
{{ post_tags(post, {class: 'tag-item-simple'}) }}
<span class="post-share right">
<a href="#share" class="icon nova-share"><span class="hidden-xs">{{__('sns.share')}}</span></a>
<a href="#comment" class="icon nova-bubbles"><span class="hidden-xs">{{__('sns.comment')}}</span></a>
<a href="#like" class="icon nova-heart2-full"><span class="hidden-xs">{{__('sns.like')}}</span></a>
</span>
</div>
<div class="divider"></div>
</header>
<div class="article-content" itemprop="articleBody" id="post-content">
{{ post.content }}
</div>
<footer class="article-footer">
<!--<time class="article-footer-updated" datetime="{{ date_xml(page.updated) }}" itemprop="dateModified">{{ __('page.last_updated', date(page.updated)) }}</time>-->
<!-- JiaThis Button BEGIN -->
<div class="jiathis_style"><a name="share"></a>
<span class="jiathis_txt icon nova-share">{{__('sns.share')}}:</span>
<a class="jiathis_button_tsina">{{__('sns.weibo')}}</a>
<a class="jiathis_button_weixin">{{__('sns.wechat')}}</a>
<a class="jiathis_button_twitter">{{__('sns.twitter')}}</a>
<a class="jiathis_button_copy">{{__('sns.copy')}}</a>
<a class="jiathis_button_ishare">{{__('sns.one')}}</a>
<a href="http://www.jiathis.com/share?uid={{theme.share.jiathis.uid}}" class="jiathis jiathis_txt jiathis_separator jtico jtico_jiathis" target="_blank">{{__('sns.more')}}</a>
<a class="jiathis_counter_style"></a>
<a name="like"></a>
<a class="jiathis_like_qzone"></a>
</div>
<!-- JiaThis Button END -->
</footer>
</article>
<div>
<nav>{{ nova_paginator2({show_name: true}) }}</nav>
{{ partial('../partial/donate') }}
{{ partial('../partial/comment') }}
</div>
其它模板
- 单页,单页与文章详情页类似。不过没有文章详情页复杂。不做详细介绍。
- 项目文档,项目文档页须借助hexo-generator-github插件使用。在此也不做详细介绍。
更多的nova layout请点击链接查看。
辅助函数
{% raw %}
在前面的主题模板中,出现了大量的{{}}
包含的文本,它是swig中调用js的方式。{{}}
包含的内容可以是hexo变量,如{{post.title}}
即是输出文章的标题。也可以是辅助函数,如__('sns.share')
即是输出健值为sns.share的国际化文本,其它一些[Nova中]定义的辅助函数有:
{% endraw %}
- page_title():返回页面标题
- page_excerpt():返回文章摘要
- post_cates():返回指定文章的分类
- post_tags():返回指定文章的标签
page_excerpt()
辅助函数代码:
// get page excerpt
hexo.extend.helper.register('page_excerpt', function(post){
var p = post ? post : this.page;
var excerpt = p.excerpt;
if (!excerpt) {
var pos = p.content.indexOf('</p>');
if (pos > 0){
excerpt = p.content.substring(0, pos + 4);
}
}
return excerpt;
});
post_cates()
辅助函数代码
// insert category of post
hexo.extend.helper.register('post_cates', function(post, options){
var o = options || {};
var _class = o.hasOwnProperty('class') ? o.class : 'category-item';
var icon = o.hasOwnProperty('icon') ? o.icon : 'glyphicon glyphicon-folder-close';
var cats = post.categories;
var _self = this;
var ret = '';
if (cats == null || cats.length == 0) {
return ret;
}
ret += '<span class="post-category">';
ret += '<i class="' + icon + '"></i><span class="hidden-xs">' + _self.__('category.label') + '</span>';
cats.forEach(function(item, i){
ret += '<a class="' + _class + '" href="' + _self.url_for_lang(item.path) + '">' + item.name + '</a>';
});
ret += '</span>';
return ret;
});
在layout/post/index.swig
中使用
{{ post_cates(post) }}
将输出以下结果: 分类软件技术Web
点击链接查看更多的Nova辅助函数
注:辅助函数放在主题scripts
目录下
主题资源
主题当中需要使用到的一些资源有css样式表,js脚本及一些图片资源。资源须放置在主题source
目录下。在生成时,这些资源会直接复制到public
根目录下,所以在主题模板中对资源的引用,直接以/
为前缀+路径加载即可,如下所示:
{{ css('css/bs/nova.css') }}
{{ js('js/script.js')}}
第三方插件
hexo是静态博客,所以像评论、分享等功能,须借助第三方插件才能实现。以评论为例,常用的评论系统有多说,友言,disqus(国外)等。如若需要使用这些第三方插件,可以到对应的官方网站上查看使用说明或集成文档。
建议
- 主题模板中尽量不要写死可能会变的东西,尽量以主题配置项的方式提供配置。比如最近文章显示几条等。
- 第三方插件脚本尽量放在之前,以免影响页面的显示。
- 选择一些比较成熟的前端框架,比如bootstrap以获得更好的兼容性。
- 支持响应式
参考
hexo: https://hexo.io Nova: http://www.ieclipse.cn/p/hexo-theme-nova