从零开始写一个Hexo主题

简介: 本文将会从零开始编写一个简单的Hexo博客主题,目的是了解一个Hexo博客主题的构成以及如何编写,因此,本示例中的博客页面样式不做过多描绘,样式主要参考 Hexo theme 中的 Noise 主题。

前言


本文将会从零开始编写一个简单的Hexo博客主题,目的是了解一个Hexo博客主题的构成以及如何编写,因此,本示例中的博客页面样式不做过多描绘,样式主要参考 Hexo theme 中的 Noise 主题。


在开始前,你需要对以下的一些知识点有必要的了解:


  • 模板引擎语法


  • CSS预处理器


  • YML语法


  • Hexo文档


本文使用的模板引擎为 ejs,使用的 CSS 预处理器为 stylus。这也是 hexo 项目预装了的 render 插件,如果想使用其他模板引擎或者其他 CSS 预处理器,可以安装相对应的 render 插件。


本文的代码:https://github.com/cccyb/theme-example


目录结构


根据下图,在themes目录下新建一个theme-example文件夹作为我们的主题目录。


640 (4).jpg


如图所示,一个hexo主题的目录主要包括以下五部分:


  • languages:用于国际化的语言文件


  • layout:主题布局模板文件


  • scripts:hexo脚本插件目录,可以编写一些辅助函数脚本


  • source:资源文件目录,包括页面样式,js脚本等


  • _config.yml:主题配置文件


局部模板


我们通过分析常见的博客网站可以知道,大部分的博客网站都由三部分组成:顶部导航栏,中间内容区域,以及底部信息展示区域。每次点击导航栏选项跳转页面时,顶部导航栏以及底部信息展示区域是不变的,只是中间的内容区域重新渲染,因此,我们可以将通用的代码抽离成局部模板以复用。


我们在layout目录下新建_partial目录,在该目录下添加head.ejs,header.ejs以及footer.ejs文件。


layout/_partial/head.ejs:


<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
    <title>标题</title>
</head>


layout/_partial/header.ejs:


<header>我是导航栏</header>


layout/_partial/footer.ejs:


<footer>我是底部信息</footer>


我们在layout中创建layout.ejs,并引入head.ejs,header/ejsfooter.ejs文件,layout.ejs文件是通用的布局文件模板,我们在后面新增的ejs文件都会继承layout.ejs,并将其内容填充到body中。


layout/layout.ejs:


<!DOCTYPE html>
<html>
<%- partial('_partial/head') %>
<body>
    <div class="container">
    <%- partial('_partial/header') %>
    <%- body %>
    <%- partial('_partial/footer') %>
    </div>
</body>
</html>


首页


首页是我们的网站加载完毕后的第一个页面。


我们在 layout 中创建 index.ejs 文件,index.ejs首页将会继承layout.ejs布局模板生成 HTML 文件。


partial()函数的作用是可以引入其他模板文件,详情参考hexo文档


layout/index.ejs:


<h1>Hello World</h1>


修改站点配置文件中的主题配置,使用我们刚刚创建的 theme-example 主题:


# Extensions
## Plugins: https://hexo.io/plugins/
## Themes: https://hexo.io/themes/
theme: theme-example


运行 hexo server 开启 Hexo 本地服务器预览,访问 http://localhost:4000/。


640 (5).jpg


编写导航栏和底部信息


前面,我们只是搭了个页面框架,接下来我们就将开始正式开始一步步的完善我们的主题,以下两个文档我们将频繁的使用,最好可以提前阅读一遍有个了解:


  • Hexo | 变量


  • Hexo | 辅助函数


layout/_partial/head.ejs:


<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
    <title><%= config.title %></title>
</head>


这里我们使用了config全局变量,该变量包含的是站点配置(即站点根目录下 _config.yml 中的配置)。除此之外,我还有将经常使用的是theme变量,该变量是主题配置(即主题根目录下 _config.yml 中的配置),其他变量参见hexo文档。


编辑导航栏部分,layout/_partial/header.ejs


<header class="header">
    <div class="title">
        <a href="<%= url_for() %>" class="logo"><%= config.title %></a>
    </div>
    <nav class="navbar">
        <ul class="menu">
            <li class="menu-item">
                <a href="/" class="menu-item-link">Home</a>
            </li>
            <li class="menu-item">
                <a href="/categories" class="menu-item-link">Categories</a>
            </li>
            <li class="menu-item">
                <a href="/tags" class="menu-item-link">Tags</a>
            </li>
            <li class="menu-item">
                <a href="/archives" class="menu-item-link">Archives</a>
            </li>
        </ul>
    </nav>
</header>


编辑底部信息部分,layout/_partial/footer.ejs


<footer>
    <p>Theme is <a href="/" target="_blank">Theme-example</a> by <a href="<%= config.url %>" target="_blank"><%= config.author %></a></p>
    <p>Powered by <a href="https://hexo.io/" target="_blank" rel="nofollow">hexo</a> &copy; <%- date(Date.now(), 'YYYY') %></p>
</footer>


这样,我们就得到了一个包含导航栏和底部信息的简单页面。


640 (6).jpg


添加主题配置


实际上我们需要让导航菜单根据我们的需要显示不同的项,上面这种写法不方便修改。所以我们会在主题的配置文件中添加导航菜单的配置。在 theme-example 下的配置文件 _config.yml,在其中添加需要配置的字段。然后可以通过 theme 这个变量来拿到该配置文件中的配置。


theme-example/_config.yml:


menu:
  home: /
  categories: /categories
  tags: /tags
  archives: /archives


这样我们就可以在 header.ejs 中使用 theme.menu 获取到导航菜单的设置。将 header.ejs 修改为:


<header class="header">
    <div class="title">
        <a href="<%= url_for() %>" class="logo"><%= config.title %></a>
    </div>
    <nav class="navbar">
        <ul class="menu">
            <% for (name in theme.menu) { %>
            <li class="menu-item">
                <a href="<%- url_for(theme.menu[name]) %>" class="menu-item-link"><%= name %></a>
            </li>
            <% } %>
        </ul>
    </nav>
</header>


这样,我们就可以动态的配置导航的信息了,我们还可以在主题配置文件中添加其他配置项供我们使用。


添加文章列表


接着我们完善首页的模板,使其能够显示文章列表。前面已经说过 Hexo 提供了各种有用的变量,在这里将会使用到 page 这个变量。page 会根据不同的页面拥有不同的属性。具体有什么属性,可以获取到哪些数据可以查看这里。


那么这里我们会使用 page 变量的 posts 属性拿到文章数据的集合。编辑 index.ejs 文件:


<section class="posts">
    <% page.posts.each(function (post) { %>
    <article class="post">
        <div class="post-title">
            <a class="post-title-link" href="<%- url_for(post.path) %>"><%= post.title %></a>
        </div>
        <div class="post-content">
            <%- post.content %>
        </div>
        <div class="post-meta">
            <span class="post-time"><%- date(post.date, "YYYY-MM-DD") %></span>
        </div>
    </article>
    <% }) %>
</section>


page.posts 中获取单篇文章的数据,并获取文章的标题,内容等数据填充到模板中。处理文章创建时间的时候使用了 date() 函数,这是 Hexo 提供的时间处理的辅助函数。


由于首页显示文章内容时使用的是 post.content,即文章的全部内容。所以首页会显示每一篇文章的内容,实际上我们并不想在首页显示那么多内容,只想显示文章的摘录。


Hexo 提供了 excerpt 属性来获取文章的摘录部分,不过这里需要在文章中添加一个 <!--more--> 标记。添加了这个标记之后,post.excerpt 将会获取到标记之前的内容。如果没有这个标记,那么 post.excerpt 会是空的。所以我们可以把首页文章内容部分的 post.content 替换成 post.excerpt


<div class="post-content">
    <%- post.excerpt %>
</div>


添加样式


到目前为止,我们完成了首页的整体页面结构,不过由于没添加css样式,因此整体页面非常难看,所以我们需要给页面加上一些样式来美化一下我们的页面。


由于 Hexo 在新建项目的时候会安装 hexo-renderer-stylus 这个插件,所以我们无需其他步骤,只需要将样式文件放到 css 文件夹中。Hexo 在生成页面的时候会将 source 中的所有文件复制到生成的 public 文件中,并且在此之前会编译 stylcss 文件。


css 文件夹中创建 style.styl,编写一些基础的样式,并把所有样式 import 到这个文件。所以最终编译之后只会有 style.css 一个文件。创建 _partial/header.styl_partial/post.styl 存放页面导航以及文章的样式,并且在 style.stylimport 这两个文件。


_partial/header.styl


.header {
    margin-top: 2em
    display: flex
    align-items: baseline
    justify-content: space-between
    .blog-title .logo {
        color: #AAA;
        font-size: 2em;
        font-family: "Comic Sans MS",cursive,LiSu,sans-serif;
        text-decoration: none;
    }
    .menu {
        margin: 0;
        padding: 0;
        .menu-item {
            display: inline-block;
            margin-right: 10px;
        }
        .menu-item-link {
            color: #AAA;
            text-decoration: none;
            &:hover {
                color: #368CCB;
            }
        }
    }
}


index.styl:


.post {
    margin: 1em auto;
    padding: 30px 50px;
    background-color: #fff;
    border: 1px solid #ddd;
    box-shadow: 0 0 2px #ddd;
}
.posts  {
    .post:first-child {
        margin-top: 0;
    }
    .post-title {
        font-size: 1.5em;
        .post-title-link {
            color: #368CCB;
            text-decoration: none;
        }
    }
    .post-content {
        a {
            color: #368CCB;
            text-decoration: none;
        }
    }
    .post-meta {
        color: #BABABA;
    }
}


style.styl:


body {
    background-color: #F2F2F2;
    font-size: 1.25rem;
    line-height: 1.5;
}
.container {
    max-width: 960px;
    margin: 0 auto;
}
@import "_partial/header";
@import "_partial/index";


最后,我们需要把样式添加到页面中,这里使用了另外一个辅助函数 css():


layout/_partial/head.ejs:


<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
    <title><%= config.title %></title>
    <%- css('css/style.styl') %>
</head>


至此,我们会看到站点的首页是这个样子的:


640 (7).jpg


添加分页


在站点的 source/_post/ 目录下存放的是我们的文章,现在我们把原本的 hello-world.md 复制黏贴 10+ 次,再查看站点首页。会发现,首页只显示了 10 篇文章。


首页显示的文章数量我们可以通过站点配置文件中的 per_page 字段来修改,但是我们不可能把所有文章都放在一页,所以我们现在来添加文章列表的分页。


新建 _partial/paginator.ejs:


<% if (page.total > 1){ %>
    <nav class="page-nav">
    <%- paginator({
        prev_text: "&laquo; Prev",
        next_text: "Next &raquo;"
    }) %>
    </nav>
<% } %>


index.ejs 中添加这个文件的内容:


...
</section>
<%- partial('_partial/paginator') %>


这里我们使用到了另外的一个辅助函数 paginator,它能够帮助我们插入分页链接,这里我们只是实现了一个最基本的分页,具体的样式可以自行添加,或者根据文档使用其他配置自定义分页。


640.jpg


添加文章详情页


文章详情页对应的布局文件是 post.ejs,新建 post.ejs:


<article class="post">
    <div class="post-title">
        <h2 class="title"><%= page.title %></h2>
    </div>
    <div class="post-meta">
        <span class="post-time"><%- date(page.date, "YYYY-MM-DD") %></span>
    </div>
    <div class="post-content">
        <%- page.content %>
    </div>
</article>


由于这里是文章的模板,所以变量 page 表示的是文章的数据,而不是首页的文章数据集合。


640 (1).jpg


添加归档页


创建归档页使用的模板文件 archive.ejs:


<section class="archive">
    <ul class="post-archive">
    <% page.posts.each(function (post) { %>
        <li class="post-item">
            <span class="post-date"><%= date(post.date, "YYYY-MM-DD") %></span>
            <a class="post-title" href="<%- url_for(post.path) %>"><%= post.title %></a>
        </li>
    <% }) %>
    </ul>
</section>
<%- partial('_partial/paginator') %>


其实结构跟首页差不多,只是不显示文章内容而已。添加归档页的样式:


css/_partial/archive.styl:


.archive {
    margin: 1em auto;
    padding: 30px 50px;
    background-color: #fff;
    border: 1px solid #ddd;
    box-shadow: 0 0 2px #ddd;
    .post-archive {
        list-style: none;
        padding: 0;
        .post-item {
            margin: 5px 0;
            .post-date {
                display: inline-block;
                margin-right: 10px;
                color: #BABABA;
            }
            .post-title {
                color: #368CCB;
                text-decoration: none;
            }
        }
    }
}


640 (2).jpg


添加分类页


分类页跟归档页也类似,相当于根据不同的分类进行归档。


分类页和标签页的模板编写比较特殊,本质上,分类页和标签页属于自定义页面,我们需要新建自定义页面模板page.ejs:


<% if (is_current(theme.menu.categories)) { %>
<%- partial('_partial/category') %>
<% } else if (is_current(theme.menu.tags)) { %>
<%- partial('_partial/tag') %>
<% } else { %>
<%- partial('_partial/custom') %>
<% } %>


这里,我们需要根据当前自定义页面的类型来决定渲染何种自定义页面模板。


新建_partial/category.ejs:


<section class="archive">
    <ul class="post-archive">
    <% site.categories.each(function (category) { %>
        <span><%= category.name %></span>
        <% category.posts.forEach(function(post) { %>
        <li class="post-item">
            <span class="post-date"><%= date(post.date, "YYYY-MM-DD") %></span>
            <a class="post-title" href="<%- url_for(post.path) %>"><%= post.title %></a>
        </li>
        <% }) %>
    <% }) %>
    </ul>
</section>


site.categories包括了站点所有的分类信息,可以遍历获取分类信息,其中category.posts又包含了该分类的所有文章信息。


需要注意的是,要想在页面中展示分类页,需要先执行hexo new page categories生成分类页面,并添加typecategories


title: categories
date: 2019-02-25 18:19:55
type: "categories"
---


后续的标签页类似。


添加标签页


标签页与分类页及其类似,只是根据标签进行归档。


新建_partial/tag.ejs:


<section class="archive">
    <ul class="post-archive">
    <% site.tags.each(function (tag) { %>
        <span><%= tag.name %></span>
        <% tag.posts.forEach(function(post) { %>
        <li class="post-item">
            <span class="post-date"><%= date(post.date, "YYYY-MM-DD") %></span>
            <a class="post-title" href="<%- url_for(post.path) %>"><%= post.title %></a>
        </li>
        <% }) %>
    <% }) %>
    </ul>
</section>


添加自定义页面


自定义页面与文章详情页类似。


新建_partial/custom.ejs:


<article class="post">
    <div class="post-title">
        <h2 class="title"><%= page.title %></h2>
    </div>
    <div class="post-meta">
        <span class="post-time"><%- date(page.date, "YYYY-MM-DD") %></span>
    </div>
    <div class="post-content">
        <%- page.content %>
    </div>
</article>


我们在主题配置文件中添加自定义页面的菜单:


menu:
  home: /
  categories: /categories
  tags: /tags
  archives: /archivesz
  about: /about


执行hexo new page about进行手动生成页面,编辑文件内容即可。


640 (3).jpg


Hexo插件


Hexo 有强大的插件系统,让我们能够轻松扩展功能而不用修改核心模块的源码。在 Hexo 中有两种形式的插件:


  • 脚本(Scripts)


  • 插件(Packages)


如果我们的代码很简单,我们可以编写脚本,只需要把 JavaScript 文件放到 scripts 文件夹,在启动时就会自动载入。简单来说,脚本文件可以相当于一些这样的的工具函数,当我们发现Hexo官方提供的函数不能满足我们的需求时,我们可以通过添加一个脚本来实现。


比如,我们现在有这样一个简单的需求,我们想给首页文章列表中的文章块添加一个背景颜色,背景颜色我们可以在文章md文件中定义,如果未定义,则随机选用一种颜色。


首先,我们先在文章md文件中顶部Front-matter添加一个color字段:


_posts/hello-world-1.md:


title: Hello World 1
date: 2019-02-12 17:49:32
categories: 分类1
tags: 
    - 标签1
color: blue
---


定义完成后,我们就可以在文章信息字段post或者page中获取到color


然后,我们需要添加一个脚本函数来根据color字段来获取文章块的背景颜色,新增scripts/getPostBgColor.js:


const arr = [ 'blue', 'purple', 'green', 'yellow', 'red', 'orange' ];
var getPostBgColor = function(color) {
  if (arr.indexOf(color) >= 0) {
    return `bg-${color}`;
  }
  return 'bg-' + randBgColor();
};
function randBgColor() {
  return arr[randomInt(0, 5)];
}
function randomInt(min, max) {
  return Math.round(Math.random() * (max - min)) + min;
}
hexo.extend.helper.register('getPostBgColor', getPostBgColor);


最后一行我们通过hexo.extend.helper.register全局注册一个脚本函数,注册完成后,我们就可以在模板文件中和Hexo官方提供的全局函数一样使用。


修改layout/index.ejs:


...
<article class="post <%= getPostBgColor(post.color) %>">
...


添加背景颜色样式,编辑css/index.styl:


...
.bg-blue {
    background-color: #6fa3ef;
}
.bg-purple {
    background-color: #bc99c4;
}
.bg-green {
    background-color: #46c47c;
}
.bg-yellow {
    background-color: #f9bb3c;
}
.bg-red {
    background-color: #e8583d;
}
.bg-orange {
    background-color: #f68e5f;
}


看下效果,Hello World 1这篇文章我们定义了color:blue,因此是蓝色,其他文章,我们未定义color,因此是随机颜色。


640 (4).jpg


这样,我们就实现了随机背景色这个小需求,当然这只是一个非常简单的例子,如果有其他复杂的需求,我们可以通过编写更加复杂的脚本来实现。


Hexo的数据DB扩展查询


我们已经知道,Hexo已经为我们预先定义了很多常用的变量供我们使用,具体可以在 Hexo | 变量 查询。但是如果系统提供的变量数据不能满足我们的要求,那我们该怎么办呢?其实我们可以通过扩展查询来获取到我们期望的数据。


其实Hexo所有的文章分类标签等等变量信息,在编译成本地静态文件之前,都是本地存储在一个db.json中的,相当于小型的本地数据库,Hexo在运行阶段,所有的数据相关操作其实都是在这个小型数据库上进行操作,其底层使用的查询引擎就是Warehouse。因此我们可以通过Warehouse的语法进行自定义扩展查询。


比如我们需要在页面的底部展示全站的最近6篇文章列表,由于Hexo首页只提供了第一页的数据,因此我们可以基于site变量进行扩展查询:


site.posts.sort({date: -1}).limit(6)


site.posts表示所有的文章,sort({date: -1})表示按创建时间倒序排列,limit(6)表示只取前6条数据,这样我们就可以拿到了全站的最近6文章信息,后续进行相应展示操作即可。


其他更多复杂的扩展查询都可以根据Warehouse语法文档进行按需扩展。


总结


其实说白了,Hexo就是把那些 Markdown 文件,按照我们编写的对应布局模板,填上对应的数据生成 HTML 页面,然后在编译的过程中将JS/CSS等文件引入HTML,然后生成每个页面的对应HMTL静态文件。


而Hexo主题的作用就是决定每个布局模板长什么样。

目录
相关文章
|
存储 对象存储 CDN
Hexo从0到1搭建博客系列04:图床的最佳实践
Hexo从0到1搭建博客系列04:图床的最佳实践
881 0
|
7月前
|
人工智能 自然语言处理 人机交互
《智能语》:十进制驱动的多模态人工语言系统构建与协同机制研究
《智能语》是全球首个融合十进制逻辑、多模态表达与东方哲学的人工语言系统,实现文字、语音、词汇、语法全链条协同。通过笔画-音素-数字三重绑定,构建“形音义理”统一的创新架构,具备语义精准、无限扩展、跨文化普适等优势,为语言学习、人机交互与文化传播提供全新范式。(238字)
374 4
|
Java C++ 开发者
【技术贴】if-else VS switch:谁才是Java条件判断的王者?
【6月更文挑战第14天】本文探讨了Java中if-else与switch语句的选择问题。if-else基于布尔逻辑,适合处理复杂逻辑,而switch在处理多分支特别是枚举类型时更高效。if-else在条件动态变化或复杂逻辑时更合适,switch则因其跳转表机制在固定选项中表现优秀。性能上,switch在大量选项时占优,但现代JVM优化后两者差异不大。选择时应考虑场景、可读性和维护性,灵活运用。理解两者特点,才能写出优雅高效的代码。
1231 0
|
缓存 Shell 应用服务中间件
PbootCms内页打不开的常见情况汇总
PbootCms内页打不开的常见情况汇总
|
Web App开发 缓存 移动开发
四万字符数带你使用 Vitepress 构建博客并部署到 github 平台
四万字符数带你使用 Vitepress 构建博客并部署到 github 平台 最近写了好多篇 Chrome 浏览器插件相关的文章,有十几二十篇,就想着构建个博客,用来放置相应的文章。 正好前段时间看到 VitePress 1.0.0 发布了,而且是用 markdown 写文章,正好写插件文章的时候文章都是 md 格式,所有用下这个然后顺便写一篇使用教程。 Chrome 插件开发博客地址:https://18055975947.github.io/extension/
596 0
|
存储 PHP 对象存储
开源免费的图床源码整理汇总-个人自用图床相册程序推荐
图床,也可以称之为相册,即存放图片的地方。 图床与普通的相册不同,图床上传的图片可以作为外链使用,也可以作为公共图片访问使用。除了支持匿名形式访问外,还支持多用户注册与登录,管理员批量管理审核等,当然高级一点功能就是自动审核、AI识别等。 图床需求不如博客建站程序那么,算是一个小众的需要,所以基本上没有什么大规模的免费图床程序。比较受欢迎的Chevereto,已经宣传转为了付费产品,虽然Github上的Chevereto还可以继续下载使用,但是作者已经铁了心要主推他的Chevereto Pro版,免费版的命运走向何方也未知。 本篇文章就来搜集整理一下开源免费的图床源码,由于很多源码都是个人开发
1909 0
|
Java Go C#
编程语言C#、C++、Java、Python、go 选择哪个好?
我想说的是,不论选择哪种编程语言,决定选择的都是你最终的目的,做选择之前,先充分调研每一个选择项,再做选择思路就会非常清晰了。
706 3
|
机器学习/深度学习 自然语言处理 搜索推荐
构建智能搜索应用:Elasticsearch与自然语言处理的融合
【8月更文第28天】随着大数据和人工智能技术的发展,用户对搜索应用的需求已经从简单的关键词匹配转向了更加智能化、人性化的交互方式。本文将探讨如何利用Elasticsearch和自然语言处理(NLP)技术构建一个能够理解用户意图并提供精准搜索结果的智能搜索系统。
1341 0
|
JavaScript iOS开发 MacOS
怎样从`MacOSX` 上完全卸载`Nodejs` ?
MacOS , Nodejs, Homebrew, nvm
4024 0
|
缓存 JavaScript API
「Vue3系列」Vue3 计算属性(computed)、监听属性(watch)
在 Vue 3 中,计算属性(Computed Properties)是一种强大的功能,它允许你声明一个依赖于其他响应式数据属性的属性,并且这个属性的值会根据其依赖的数据的变化而自动更新。计算属性是基于它们的依赖关系进行缓存的,只有在它的相关依赖发生改变时才会重新求值。
2086 0