Hexo

予早 2024-05-26 22:19:36
Categories: Tags:

https://hexo.io/

https://hexo.io/docs/

安装

  1. 安装nodejs

  2. 配置git以及github

  3. 初始化hexo,自定义名称,例如blog-hexo-helper

    npm install hexo-cli -g
    hexo init blog-hexo-helper
    cd blog-hexo-helper
    npm install
    hexo server
    
  4. 图片资源存放,采用相对路径方案,适合Typora编辑方式,https://moeci.com/posts/hexo-typora/

    # 1.将_config.yml中post_asset_folder置为true
    # 2.安装hexo-asset-img插件
    npm install hexo-asset-img --save
    
  5. hexo与blog内容分离

    • 在GitHub上创建blog-hexo-helper仓库,用于存放hexo内容

    • 博客主要内容存放于blog-hexo-helper/source/_posts下,将_posts作为submodule,博客主内容存放至temp_blog仓库

      git submodule add https://github.com/xxxxxx/blog _posts
      
  6. 博客部署采用git方式

    # 安装插件
    npm install hexo-deployer-git --save
    

    配置对应配置

    # Deployment
    ## Docs: https://hexo.io/docs/one-command-deployment
    deploy:
      type: git
      repo: https://github.com/BlogSeriesHelper/blog-static-page.git
      branch: main
    
  7. 一键部署脚本

    :deploy.bat
    
    call hexo clean
    cd ./source/_posts
    git checkout .
    git clean -df
    git checkout master
    git pull
    cd ../../
    call hexo deploy
    call hexo clean
    pause
    
    :调试没问题后可以把下方注释
    pause
    
  8. 工作流程

    • 使用Typora编辑器在temp_blog文件夹中写博客,写完推送到对应GitHub仓库
    • 执行deploy.bat部署博客

相关命令

# 创建新文章
hexo new "My New Post"
# 启动服务,简写hexo s
hexo server
# 生成页面,简写hexo g
hexo generate
# 部署页面,简写hexo d
hexo deploy
# 清理生成的页面
hexo clean

# 从模板生成文章,默认post(帖子),draft(草稿)、page(页面)可选
hexo n [layout_name] title

相关配置

# 跳过渲染的页面
skip_render: 
  # 单个文件夹下全部文件
  - skip_render: demo/*
  # 单个文件夹下指定类型文件
  - skip_render: demo/*.html
  # 单个文件夹下全部文件以及子目录
  - skip_render: demo/**

YAML Front Matter

YAML格式的元数据块,由双行的连字符(---)包裹,元数据例如标题、日期、标签等,某些Markdown处理器或者静态页面生成器(例如ekyll、Hexo、Hugo)识别该元数据并用于相关用途,并不会 作为正文显示在HTML页面中

Typora中空元数据效果

示例

---
title: Hexo
date: 2024-05-26 22:19:36
tags: 
---

https://hexo.io/
...

其他可参考文章

https://blog.csdn.net/zemprogram/article/details/104288872

主题开发

学习步骤:

  1. 了解初始主题的结构,通过初始主题结构了解主题
  2. 开发一个小demo

以默认主题 Landscape 分析 Hexo 主题结构

https://github.com/hexojs/hexo-theme-landscape

https://hexojs.github.io/hexo-theme-landscape/

Landscape 是 hexo 默认主题,在初始化时会被以npm包的形式安装,node_modules\hexo-theme-landscape

hexo-theme-landscape
├── LICENSE                                     许可证
├── README.md                                   README.md
├── _config.yml
├── languages                                   多语言支持
│   ├── de-DE.yml
│   ├── de.yml
│   ├── default.yml
│   ├── en-GB.yml
│   ├── en-US.yml
│   ├── en.yml
│   ├── es-ES.yml
│   ├── es.yml
│   ├── fr-FR.yml
│   ├── fr.yml
│   ├── hu-HU.yml
│   ├── hu.yml
│   ├── it-IT.yml
│   ├── it.yml
│   ├── ja-JP.yml
│   ├── ja.yml
│   ├── ko-KR.yml
│   ├── ko.yml
│   ├── mn-MN.yml
│   ├── mn.yml
│   ├── nl-NL.yml
│   ├── nl.yml
│   ├── no.yml
│   ├── pt-PT.yml
│   ├── pt.yml
│   ├── ru-RU.yml
│   ├── ru.yml
│   ├── th-TH.yml
│   ├── th.yml
│   ├── tr.yml
│   ├── zh-CN.yml
│   └── zh-TW.yml
├── layout                                      布局
│   ├── _partial                                布局由若干部分组成
│   │   ├── after-footer.ejs                    所有页面html的最后面应用 js 部分
│   │   ├── archive-post.ejs                    对所有博客的归档
│   │   ├── archive.ejs                         包括分类,标签以及博客的归档
│   │   ├── article.ejs                         展示每篇博客的内容
│   │   ├── footer.ejs                          每页内容的最下面展示的内容,比如copyright等
│   │   ├── gauges-analytics.ejs                对每篇博客的字数统计分析
│   │   ├── google-analytics.ejs                对每篇博客的谷歌统计分析
│   │   ├── head.ejs                            html的头部内容
│   │   ├── header.ejs
│   │   ├── mobile-nav.ejs                      移动端时的导航
│   │   ├── post                                博客博文的组件
│   │   │   ├── category.ejs                    分类
│   │   │   ├── date.ejs                        日期
│   │   │   ├── gallery.ejs                     日历
│   │   │   ├── nav.ejs
│   │   │   ├── tag.ejs
│   │   │   └── title.ejs
│   │   └── sidebar.ejs                         布局中的侧边栏
│   ├── _widget                                 小组件,侧边导航栏组件
│   │   ├── archive.ejs                         侧边栏里的归档
│   │   ├── category.ejs                        侧边栏里的分类
│   │   ├── recent_posts.ejs                    侧边栏里的最近博客
│   │   ├── tag.ejs                             侧边栏里的标签
│   │   └── tagcloud.ejs                        侧边栏里的标签云
│   ├── archive.ejs                             博客的归档
│   ├── category.ejs                            博客的分类
│   ├── index.ejs                               博客的主页
│   ├── layout.ejs                              博客的布局,该文件控制整体布局
│   ├── page.ejs                                博客
│   ├── post.ejs                                博客中博文的页面,由 markdown 转化为 html 的页面
│   └── tag.ejs                                 博客的标签
├── package.json
├── scripts                                     脚本
│   └── fancybox.js                             启动 hexo 时会运行 fancybox.js
└── source                                      源文件
    ├── css                                     css
    │   ├── _extend.styl
    │   ├── _partial
    │   │   ├── archive.styl
    │   │   ├── article.styl
    │   │   ├── comment.styl
    │   │   ├── footer.styl
    │   │   ├── header.styl
    │   │   ├── highlight.styl
    │   │   ├── mobile.styl
    │   │   ├── sidebar-aside.styl
    │   │   ├── sidebar-bottom.styl
    │   │   └── sidebar.styl
    │   ├── _util
    │   │   ├── grid.styl
    │   │   └── mixin.styl
    │   ├── _variables.styl
    │   ├── images
    │   │   └── banner.jpg
    │   └── style.styl
    ├── fancybox
    │   ├── jquery.fancybox.min.css
    │   └── jquery.fancybox.min.js
    └── js                                      js
        ├── jquery-3.6.4.min.js
        └── script.js

1.5 hexo 主题工作整体流程
Hexo 启动后,

读 scripts 下所有脚本并执行这些脚本;
读取 layout 目录下 layout.ejs 文件;
根据 <%-body%> 在 layout.ejs 的位置进行渲染,包括主页的渲染、分类的渲染、归档的渲染以及自定义页面的渲染。

https://blog.csdn.net/smileyan9/article/details/124268248

开发hexo主题 book

npm install hexo-cli -g
hexo init hexo-theme-dev
cd hexo-theme-dev
npm install
hexo server

访问 http://localhost:4000 可以看到默认页面

接下来创建一个新的主题 book

hexo 的主题会放在hexo-theme-dev\themes中,新建一个hexo-theme-book文件夹,

hexo-theme-book
├── _config.yml
├── layout			布局
│   ├── index.ejs	主页,暂时为空白文本文件
│   ├── layout.ejs	布局,暂时为空白文本文件
│   └── post.ejs	博文,暂时为空白文本文件
└── source			源文件
    ├── css			css文件夹
    └── js			js文件夹

随后将hexo-theme-dev的配置文件_config.yml中主题配置为hexo-theme-book,重新启动hexo server,访问 http://localhost:4000 可以看到为空白文件。

模板渲染采用ejs进行,先写一个简单示例

https://ejs.bootcss.com/

标签 含义
<%%> 脚本标签 用于流程控制
<%_ 删除前面的空格符
<%= 输出数据到模板
<%- 输出非转义数据到模板
<%# 注释
<%% 输出字符串『<%』
-%> 结束时,删除换行符
_%> 结束时,删除空格符

模板中采用hexo提供的全局变量和辅助函数帮助渲染内容

https://hexo.io/zh-cn/docs/variables

变量 描述
site 总体变量,几乎都是从这里开始的
site.posts 所有文章
site.posts.path 文章路径,带日期的
site.posts.slug 文章路径,根据项目文件夹的路径来的
site.posts._id 文章的唯一 id,后面会用于 active 对比
site.posts.title 文章的标题
site.posts.date 文章的时间
page.date 在直接访问文章路径下,文章的时间
page.title 在直接访问文章路径下,文章的标题
page._id 在直接访问文章路径下,文章的的唯一 id,后面会用于 active 对比
page.content 引入对应文章的正文
config.xxx 总体配置文件的引用 _config.yml
theme.xxx 主题配置文件 theme._config.yml
<%- body %> 同时引入 post.ejs 和 index.ejs
<%- css(path, …) %> 引入 css 文件
<%- js(path, …) %> 引入 js 文件

layout.ejs

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta charset="utf-8">
</head>
<body>

    layout 页面详细内容

    <%- include("index.ejs") %>

    <%- include("post.ejs") %>

    <%  %>

    <% 
        var test = "执行脚本,定义一个变量 test,随后让 test 在";
    %>

    <%- test %>

    <br />

    <% site.posts.forEach(function(post){ %>

        <%- post.path %>
        <br />
        <%- post.slug %>
        <br />
        <%- post._id %>
        <br />
        <%- post.title %>
        <br />
        <%- post.date %>
        <br />

        <a href="/<%- post.path %>"><%- post.title %></a>

    <% }); %>

  <%- body %>

</body>
</html>

index.ejs

index 页面详细内容

post.ejs

post 页面详细内容

<%- page.content %>

规划一下页面布局,基本的一个 book 形式如图:

主题配置文件

首先为主题配置文件,_config.yml

title: A Book

author: yuzaoyah

menus: 
  主页: /
  关于: /about

links:
  Site: https://blog.yuzaoyah.site
  Github: https://github.com/yuzaoyah

主布局文件

layout/layout.ejs

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <!-- 引入主 css 文件 -->
    <%- css('css/main.css') %>
    <title><%- theme.title %></title>
</head>
<body>
    <div id="main">
        <!-- 侧边栏 -->
        <aside id="#aside">
            <%- include('aside.ejs') %>
        </aside>
        <!-- 导航 -->
        <nav>
            <%- include('nav.ejs') %>
        </nav>
        <!-- 正文 -->
        <div id="content">
              <%- body %>
        </div>
    </div>
    <!-- 引入主 js 文件 -->
    <%- js('js/main.js') %>
</body>
</html>

source/css/main.css

开发时以不同颜色区分布局区域,便于设计页面

* {
    margin: 0;
    padding: 0;
    border: 0;
}

html, body {
    height: 100%;
}

aside {
    width: 320px;
    height: 100%;
    position: fixed;
    background: #262a30;
}

nav {
    height: 50px;
    background: #b9e1b1;
}

#content {
    height: 100%;
    padding-left: 320px;
    background: #f9f7ed;
}

侧边栏

树状结构侧边栏

引入一个图片库增加效果,Font Awesome,https://fontawesome.dashgame.com/,该库放到主题的`source/lib`目录下

layout.ejs 的 head 标签中引入

 <%- css('lib/font-awesome/css/font-awesome.min.css') %>

aside.ejs

<!-- 搜索栏 -->
<div id="search">
    <input class="search-input" type="text" placeholder="search">
    <i class="fa fa-search"></i>
</div>

<!-- 侧边目录栏 -->
<div id="tree">
    <%
        <!-- 将路径转换成 tree 目录结构 -->
        const pathToTree = (input) => {
            var output = [];
            input.forEach(function(post){
                <!-- 用来分割去掉时间的路径 -->
                var chain = post.slug.split("/");
                var currentNode = output;
                for (var j = 0; j < chain.length; j++) {
                    if (chain[j] === '') {
                        break;
                    }
                    var wantedNode = chain[j];
                    var lastNode = currentNode;

                    for (var k = 0; k < currentNode.length; k++) {
                        if (currentNode[k].title == wantedNode) {
                            currentNode = currentNode[k].children;
                            break;
                        }
                    }

                    if (lastNode == currentNode) {
                        var newNode = currentNode[k] = { post: post, title: wantedNode, children: [] };
                        currentNode = newNode.children;
                    } else {
                        delete currentNode.children
                    }
                }
            });
            return output;
        }

        <!-- console.log(output) -->
    %>

    <%
        <!-- 递归输出侧边栏目录 tree -->
        const showTree = (input) => {

            <!-- 按 tile ascii 排序 -->
            input.sort(function(a, b){
                var len = a.title.length > b.title.length ? a.title.length : b.title.length;
                for ( var i = 0; i < len; i++ ) {
                    res = a.title[i].charCodeAt() - b.title[i].charCodeAt();
                    if ( res ) { return res }
                }
            });

            <!-- 循环输出 html 结构 -->
            input.forEach(function(node) {
                if ( node.children == 0 ) {

    %>
                    <ul>
                        <li class="file<%- (is_post() && node.post._id == page._id) ? ' active' : '' %>">
                            <a href="<%- config.root %><%- node.post.path %>">
                                <i class="fa fa-file"></i>
                                <%- node.title %>
                            </a>
                        </li>
                    </ul>
    <%
                }
                else {
    %>
                    <ul>
                        <li class="directory">
                            <a href="#" class="directory">
                                <i class="fa fa-folder"></i>
                                <%- node.title %>
                            </a>
                            <%- showTree(node.children) %>
                        </li>

                    </ul>
    <%
                }
            });
        }

        showTree(pathToTree(site.posts))
    %>
</div>

<!-- 文章大纲 -->
<div id="toc"></div>

在main.css中增加aside的css

<%- css('css/aside.css') %>

source/css/aside.css

/* #################### 侧边栏 #################### */
/* 搜索框 */
aside input {
    width: 290px;
    height: 50px;
    margin: 0;
    border: 0;
    padding: 0;
    left: 0;
    font-size: 14px;
    background: #131417;
    text-indent: 20px;
    outline: none;
    color: #87daff;
    position: absolute;
}

/* 搜索图标 */
aside #search i.fa.fa-search {
    position: absolute;
    top: 0;
    right: 0;
    color: #757575;
    font-size: 20px;
    width: 40px;
    height: 50px;
    background: #131417;
    text-align: center;
    line-height: 2.5;
}

/* 滚动条 */
aside {
    overflow-y: scroll;
}

/* 树目录位置 */
aside #tree {
    padding-top: 55px
}

/* 树目录 */
aside ul {
    padding: 0px 5px 5px 20px;
}
aside ul li {
    list-style: none;
    line-height: 25px;
}
aside ul li a {
    color: #999;
    font-size: 14px;
    text-decoration: none

}
aside ul li a:hover {
    color: #ccc;
    border-bottom: 1px solid #ccc;
    cursor:pointer;
}
aside #tree .active a,
aside #tree .active a:hover {
    color: #87daff;
    border-bottom: 1px solid #87daff;
}

aside #tree i.fa {
    padding-right: 5px;
}

/* #################### 侧边栏 #################### */


/* #################### 文章索引 #################### */

aside #toc a.read{
    /*color: #e06c75;*/
    color: #87daff;
    border-bottom: 0;
    -webkit-transition: 0.5s;
    -moz-transition: 0.5s;
    -ms-transition: 0.5s;
    -o-transition: 0.5s;
}

aside #toc a{
    color: #999;
    border-bottom: 0;
    -webkit-transition: 0.2s;
    -moz-transition: 0.2s;
    -ms-transition: 0.2s;
    -o-transition: 0.2s;
}

aside #toc a:hover{
    /*color: #87daff;*/
    color: #e5c07b;
    padding-left: 20px;
    -webkit-transition: 0.1s;
    -moz-transition: 0.1s;
    -ms-transition: 0.1s;
    -o-transition: 0.1s;
}

/* #################### 文章索引 #################### */
<%- js('https://code.jquery.com/jquery-3.7.1.min.js') %>
<%- js('js/aside.js') %>
$(document).ready(function () {
    hljs.initHighlightingOnLoad();
    clickTreeDirectory();
    serachTree();
    pjaxLoad();
    showArticleIndex();
});

function showArticleIndex() {
    // 先刷一遍文章有哪些节点,把 h1 h2 h3 加入列表,等下循环进行处理。
    // 如果不够,可以加上 h4 ,只是我个人觉得,前 3 个就够了,出现第 4 层就目录就太长了,太细节了。
    var h1List = h2List = h3List = [];
    var labelList = $("#article").children();
    for ( var i=0; i<labelList.length; i++ ) {
        if ( $(labelList[i]).is("h1") ) {
            h2List = new Array();
            h1List.push({node: $(labelList[i]), id: i, children: h2List});
        }

        if ( $(labelList[i]).is("h2") ) {
            h3List = new Array();
            h2List.push({node: $(labelList[i]), id: i, children: h3List});
        }

        if ( $(labelList[i]).is("h3") ) {
            h3List.push({node: $(labelList[i]), id: i, children: []});
        }
    }

    // 闭包递归,返回树状 html 格式的文章目录索引
    function show(tocList) {
        var content = "<ul>";
        tocList.forEach(function (toc) {
            toc.node.before('<span class="anchor" id="_label'+toc.id+'"></span>');
            if ( toc.children == 0 ) {
                content += '<li><a href="#_label'+toc.id+'">'+toc.node.text()+'</a></li>';
            }
            else {
                content += '<li><a href="#_label'+toc.id+'">'+toc.node.text()+'</a>'+show(toc.children)+'</li>';
            }
        });
        content += "</ul>"
        return content;
    }

  // 最后组合成 div 方便 css 设计样式,添加到指定位置
    $("aside #toc").empty();
    $("aside #toc").append(show(h1List));

    // 点击目录索引链接,动画跳转过去,不是默认闪现过去
    $("#toc a").on("click", function(e){
        e.preventDefault();
        // 获取当前点击的 a 标签,并前触发滚动动画往对应的位置
        var target = $(this.hash);
        $("body, html").animate(
            {'scrollTop': target.offset().top},
            500
        );
    });

    // 监听浏览器滚动条,当浏览过的标签,给他上色。
    $(window).on("scroll", function(e){
        var anchorList = $(".anchor");
        anchorList.each(function(){
            var tocLink = $('#toc a[href="#'+$(this).attr("id")+'"]');
            var anchorTop = $(this).offset().top;
            var windowTop = $(window).scrollTop();
            if ( anchorTop <= windowTop+50 ) {
                tocLink.addClass("read");
            }
            else {
                tocLink.removeClass("read");
            }
        });
    });
}

nav.ejs

<ul id="menu">
    <!-- 内部链接本页面直接跳转 -->
    <% for ( menu in theme.menus ) { %>
    <li class="menu-item">
        <a href="<%- theme.menus[menu] %>" class="menu-item-link"><%- menu %></a>
    </li>
    <% } %>

    <!-- 外部链接打开新的窗口跳转 -->
    <% for ( link in theme.links ) { %>
    <li class="menu-item">
        <a href="<%- theme.links[link] %>" class="menu-item-link" target="_blank"><%- link %></a>
    </li>
    <% } %>

</ul>

在main.css中为nav.ejs增添css

<%- css('css/nav.css') %>
/* #################### 导航 #################### */
nav #menu {
    float: right;
    padding-right: 20px;
}
nav ul li {
    float: left;
    padding: 10px;
    list-style: none;
}
nav ul li a{
    color: #555;
    font-size: 12px;
    text-decoration: none;
}
nav ul li a:hover {
    border-bottom: 1px solid;
}
/* #################### 导航 #################### *

post

post.ejs

<div>
    <span id="post-author">作者: <%- theme.author %></span>
    <span id="post-date"><%- date(page.date, "YYYY-MM-DD HH:mm:ss") %></span>
</div>

<div id="article">
    <%- page.content %>
</div>

main.css 中增加文章 css

<%- css('css/post.css') %>
/* #################### 文章 #################### */
/* 作者 时间 相关 */
#post-author {
    font-size: 9px;
    position: absolute;
    top: 60px;
    right: 30px;
    color: #999;
}

#post-date {
    font-size: 9px;
    position: absolute;
    top: 80px;
    right: 30px;
    color: #999;
}

/* 文章各个元素间隔 */
#article {
    padding: 30px;
}
#article * {
    margin: 30px 0;
}


#article h1,
#article h2,
#article h3,
#article h4,
#article h5,
#article h6 {
    line-height: 40px;
    margin: 20px 0 15px;
}


#article h1 {
    font-size: 32px;
    font-weight: 900;
    padding-bottom: 10px;
    border-bottom: 2px solid #e06c75;
}

#article h2 {
    font-size: 25px;
    border-left: 3px solid #73b1e0;
    padding-left: 10px;
}

#article h3 {
    font-size: 22px
}

#article a {
    color: #e06c75;
    text-decoration: none;
}

#article a:hover {
    border-bottom: 1px solid;
}

#article h1 a:hover{
    border-bottom: none;
}

#article code {
    border-radius: 3px;
    box-shadow: 0px 0px 5px #999;
}

#article img {
    max-width: 100%;
    height: auto;
    border-radius: 5px;
    box-shadow: 0px 0px 8px #999;
}

#article pre {
    font-size: 14px;
    margin: 20px 0 15px;
}

#article p {
    margin: 0 0 10px;
    line-height: 30px
}

/* 表格处理 */
#article table {
  width: 100%;
  border: 0;
  margin: 20px 0 50px 0;
  border-collapse: collapse;
  border-spacing: 0;
  line-height: 35px;
  border-radius: 8px;
  box-shadow: 0px 0px 5px #999;
}

#article table th {
  background: #73b1e0;
  font-weight: 800;
  font-size: 18px;
  text-align: left;
  line-height: 35px;
  color: #FFF;
}

#article table tr:nth-child(odd) {
  background: #F4F4F4;
}

#article table tr:hover,
#article table td:hover {
  background: #badbf5;
  color: #FFF;
}

#article table td, table th {
  padding: 5px 20px 5px 20px
}

#article table tr:first-child th:first-child {
  border-top-left-radius: 3px;
}

#article table tr:first-child th:last-child {
  border-top-right-radius: 3px;
}

#article table tr:last-child td:first-child {
  border-bottom-left-radius: 3px;
}

#article table tr:last-child td:last-child {
  border-bottom-right-radius: 3px;
}

/* #################### 文章 #################### */