Hexo添加自定义分类菜单项并定制页面布局

2021-06-20: 使用Hexo扩展的方式可以更简洁地定制页面,避免了本文实现的一些弊端。Hexo添加自定义分类菜单项并定制页面布局(简洁版)

2020-07-22: 更新hexo-generator-category到v1.0.0后,对应的generator.js代码进行了修改。 2019-11-27: 更新NexT v7.5.0的customcategory.swig文件,与老版本v6.6.0不同。

希望在Hexo的NexT主题中增加自定义分类的菜单,即一个指向特定分类的链接,且页面显示的是类似主页的标题+摘要形式。Hexo的可定制化很强,绝大多数需求都可以直接通过配置满足。真的有额外需求,因为它开源,可以直接在源码上折腾。:-)

添加自定义分类菜单

添加自定义分类菜单项比较容易,因为hexo-generator-category会为每个分类生成一个静态页面,并放在"/categories/分类名"之下,只不过它渲染出来的形式是时间线,本节先介绍如何增加此种形式的自定义菜单。

添加分类映射

如果分类名非英文而希望对应分类的url为英文,需要编辑站点配置文件_config.yml中的catetory_map:

# Category & Tag
default_category: misc
category_map:
  楼市分析: realestate
tag_map:

添加自定义菜单按钮

为了让每个页面都生成新的菜单项,编辑主题配置文件_config.yml中的menu:

menu:
   #about: /about/ || user
   tags: /tags/ || tags
   categories: /categories/ || th
   楼市分析: /categories/realestate/ || line-chart
   archives: /archives/ || archive
其中menu菜单每行的格式为"Key: /link/ || icon"。需要自定义icon可以在对应版本的fontawesome icon列表中找。

编辑站点对应的语言配置文件

假设站点使用英文,则须编辑主题目录下languages/en.yml(根据站点语言选择对应的yaml文件)中的menu,增加最后一行:

menu:
  home: Home
  archives: Archives
  categories: Categories
  tags: Tags
  about: About
  search: Search
  schedule: Schedule
  sitemap: Sitemap
  commonweal: Commonweal 404
  realestate: 楼市分析

定制自定义分类页面布局

上节所述方法只能生成时间线式分类页面,而我希望在自定义菜单项所指向的页面显示该分类下所有文章的标题+摘要,和站点的主页形式一致。

阅读了搭建 Hexo 博客的填坑经历和必要的优化的方案,博主直接修改了主题目录下的layout/cateory.swig模板以完成此功能。不过这样做有一个缺点是改变了菜单中Categories中每个分类对应的页面布局,即也由时间线变成了摘要布局。

考虑到分类页面由hexo-generator-category生成,读了下它的源码发现非常简单,可以通过增加新模板的形式,在不改变原有菜单时间线布局的前提下生成自定义布局的分类页面。

修改hexo-generator-category源码

hexo-generator-category v0.1.3

hexo-generator-category的主要逻辑写在node_modules/hexo-generator-category/lib/generator.js:

'use strict';

var pagination = require('hexo-pagination');

module.exports = function(locals){
  var config = this.config;
  var perPage = config.category_generator.per_page;
  var paginationDir = config.pagination_dir || 'page';

  return locals.categories.reduce(function(result, category){
    if (!category.length) return result;

    var posts = category.posts.sort('-date');
    var data = pagination(category.path, posts, {
      perPage: perPage,
      layout: ['category', 'archive', 'index'],
      format: paginationDir + '/%d/',
      data: {
        category: category.name
      }
    });

    return result.concat(data);
  }, []);
};
发现分类页面的layout为category,生成文件的路径在category.path。可以通过增加新的布局,并将生成的页面放在次级目录之下。

具体实现:添加datacustom变量,设置新增的分类页面路径为category.path + 'custom/',设置其layout为categorycustom,并加入到返回值中,修改后的代码如下:

'use strict';

var pagination = require('hexo-pagination');

module.exports = function(locals){
  var config = this.config;
  var perPage = config.category_generator.per_page;
  var paginationDir = config.pagination_dir || 'page';

  return locals.categories.reduce(function(result, category){
    if (!category.length) return result;

    var posts = category.posts.sort('-date');
    var data = pagination(category.path, posts, {
      perPage: perPage,
      layout: ['category', 'archive', 'index'],
      format: paginationDir + '/%d/',
      data: {
        category: category.name
      }
    });
    var datacustom = pagination(category.path + 'custom/', posts, {
      perPage: perPage,
      layout: ['categorycustom', 'archive', 'index'],
      format: paginationDir + '/%d/',
      data: {
        category: category.name
      }
    });

    return result.concat(data, datacustom);
  }, []);
};

hexo-generator-category v1.0.0

v1.0.0版本的插件代码和v0.1.3略有不同,node_modules/hexo-generator-category/lib/generator.js:

'use strict';

const pagination = require('hexo-pagination');

module.exports = function(locals) {
  const config = this.config;
  const perPage = config.category_generator.per_page;
  const paginationDir = config.pagination_dir || 'page';
  const orderBy = config.category_generator.order_by || '-date';

  return locals.categories.reduce((result, category) => {
    if (!category.length) return result;

    const posts = category.posts.sort(orderBy);
    const data = pagination(category.path, posts, {
      perPage,
      layout: ['category', 'archive', 'index'],
      format: paginationDir + '/%d/',
      data: {
        category: category.name
      }
    });

    return result.concat(data);
  }, []);
};

实现思路与上面相同,不再赘述:

'use strict';

const pagination = require('hexo-pagination');

module.exports = function(locals) {
  const config = this.config;
  const perPage = config.category_generator.per_page;
  const paginationDir = config.pagination_dir || 'page';
  const orderBy = config.category_generator.order_by || '-date';

  return locals.categories.reduce((result, category) => {
    if (!category.length) return result;

    const posts = category.posts.sort(orderBy);
    const data = pagination(category.path, posts, {
      perPage,
      layout: ['category', 'archive', 'index'],
      format: paginationDir + '/%d/',
      data: {
        category: category.name
      }
    });
    const datacustom = pagination(category.path + 'custom/', posts, {
      perPage: perPage,
      layout: ['categorycustom', 'archive', 'index'],
      format: paginationDir + '/%d/',
      data: {
        category: category.name
      }
    });

    return result.concat(data, datacustom);
  }, []);
};

添加新的页面模板

默认分类页面布局在主题目录的layout/category.swig:

{% extends '_layout.swig' %}
{% import '_macro/post-collapse.swig' as post_template %}
{% import '_macro/sidebar.swig' as sidebar_template %}

{% block title %}{{ __('title.category') }}: {{ page.category }} | {{ title }}{% endblock %}

{% block content %}

  {######################}
  {### CATEGORY BLOCK ###}
  {######################}
  <div class="post-block category">

    <div id="posts" class="posts-collapse">
      <div class="collection-title">
        <{% if theme.seo %}h2{% else %}h1{% endif %}>{#
        #}{{ page.category }}{#
        #}<small>{{  __('title.category')  }}</small>
        </{% if theme.seo %}h2{% else %}h1{% endif %}>
      </div>

      {% for post in page.posts %}
        {{ post_template.render(post) }}
      {% endfor %}
    </div>

  </div>
  {##########################}
  {### END CATEGORY BLOCK ###}
  {##########################}

  {% include '_partials/pagination.swig' %}

{% endblock %}

{% block sidebar %}
  {{ sidebar_template.render(false) }}
{% endblock %}
此文件生成的每个post的为"posts-collapse",即时间线形式。

NexT v6.6.0

上节新增的页面采用布局"categorycustom",可在layout目录下添加categorycustom.swig文件:

{% extends '_layout.swig' %}
{% import '_macro/post.swig' as post_template %}
{% import '_macro/sidebar.swig' as sidebar_template %}

{% block title %}{{ __('title.category') }}: {{ page.category }} | {{ config.title }}{% endblock %}

{% block content %}
  <section id="posts" class="posts-expand">
    {% for post in page.posts %}
      {{ post_template.render(post, true) }}
    {% endfor %}
  </section>

  {% include '_partials/pagination.swig' %}
{% endblock %}

{% block sidebar %}
  {{ sidebar_template.render(false) }}
{% endblock %}

NexT v7.5.0

升级了Hexo到v4.0.0,NexT主题到v7.5.0,主题的升级还是费点劲,只能用git pull手动升级,且还要resolve一堆config的conflicts。之前没用它推荐的两种数据文件管理方式(Data Files)。

在Hexo v4.0.0,NexT v7.5.0下如果用之前的categorycustom.swig文件会报如下错误:

ERROR Template render error: (finisky/themes/next/layout/categorycustom.swig) [Line 4, Column 70] Error: Unable to call url_for, which is undefined or falsey Template render error: (finisky/themes/next/layout/categorycustom.swig) [Line 4, Column 70] Error: Unable to call url_for, which is undefined or falsey at Object._prettifyError (finisky/node_modules/nunjucks/src/lib.js:36:11) at finisky/node_modules/nunjucks/src/environment.js:567:19 at eval (eval at _compile (finisky/node_modules/nunjucks/src/environment.js:637:18), :19:11) at finisky/node_modules/nunjucks/src/environment.js:617:9 at Template.root [as rootRenderFunc] (eval at _compile (finisky/node_modules/nunjucks/src/environment.js:637:18), :516:3) at Template.getExported (finisky/node_modules/nunjucks/src/environment.js:615:10) at eval (eval at _compile (finisky/node_modules/nunjucks/src/environment.js:637:18), :18:5) at Environment.getTemplate (finisky/node_modules/nunjucks/src/environment.js:279:9) at eval (eval at _compile (finisky/node_modules/nunjucks/src/environment.js:637:18), :16:5) at Environment.getTemplate (finisky/node_modules/nunjucks/src/environment.js:279:9) at Template.root [as rootRenderFunc] (eval at _compile (finisky/node_modules/nunjucks/src/environment.js:637:18), :9:5) at Template.render (finisky/node_modules/nunjucks/src/environment.js:556:10) at finisky/themes/next/scripts/renderer.js:32:29 at Theme._View.View._compiled.locals [as _compiled] (finisky/node_modules/hexo/lib/theme/view.js:123:48) at Theme._View.View.View.render (finisky/node_modules/hexo/lib/theme/view.js:29:15) at finisky/node_modules/hexo/lib/hexo/index.js:365:21 at tryCatcher (finisky/node_modules/bluebird/js/release/util.js:16:23) at finisky/node_modules/bluebird/js/release/method.js:15:34 at RouteStream._read (finisky/node_modules/hexo/lib/hexo/router.js:123:3) at RouteStream.Readable.read (stream_readable.js:457:10) at resume (_stream_readable.js:933:12) at process.internalTickCallback (internal/process/next_tick.js:72:19)

更新后的layout目录中的categorycustom.swig文件:

{% extends '_layout.swig' %}
{% import '_macro/post-collapse.swig' as post_template with context %}
{% import '_macro/sidebar.swig' as sidebar_template with context %}

{% block title %}{{ __('title.category') }}: {{ page.category }} | {{ title }}{% endblock %}

{% block content %}
  <section id="posts" class="posts-expand">
    {%- for post in page.posts.toArray() %}
      {{ partial('_macro/post.swig', {post: post, is_index: true}) }}
    {%- endfor %}
  </section>

  {% include '_partials/pagination.swig' %}
{% endblock %}

{% block sidebar %}
  {{ sidebar_template.render(false) }}
{% endblock %}

编辑主题配置文件

由于添加了新的页面并存放于category/custom目录中,再次编辑主题配置文件_config.yml中的menu:

menu:
  home: / || home
  #about: /about/ || user
  tags: /tags/ || tags
  categories: /categories/ || th
  楼市分析: /categories/realestate/custom/ || line-chart
  archives: /archives/ || archive
至此,大功告成。

Known Issues

  1. 升级hexo-generator-category后可能需要重新修改源码
  2. 升级NexT主题后也要修改源码。。。
  3. 多生成了几个页面

References

搭建 Hexo 博客的填坑经历和必要的优化 Generator|Hexo