安装
安装nodejs
配置git以及github
初始化hexo,自定义名称,例如blog-hexo-helper
npm install hexo-cli -g hexo init blog-hexo-helper cd blog-hexo-helper npm install hexo server图片资源存放,采用相对路径方案,适合Typora编辑方式,https://moeci.com/posts/hexo-typora/
# 1.将_config.yml中post_asset_folder置为true # 2.安装hexo-asset-img插件 npm install hexo-asset-img --savehexo与blog内容分离
在GitHub上创建blog-hexo-helper仓库,用于存放hexo内容
博客主要内容存放于blog-hexo-helper/source/_posts下,将
_posts作为submodule,博客主内容存放至temp_blog仓库git submodule add https://github.com/xxxxxx/blog _posts
博客部署采用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一键部署脚本
: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工作流程
- 使用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
主题开发
学习步骤:
- 了解初始主题的结构,通过初始主题结构了解主题
- 开发一个小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进行,先写一个简单示例
| 标签 | 含义 | |
|---|---|---|
| <%%> | 脚本标签 | 用于流程控制 |
| <%_ | 删除前面的空格符 | |
| <%= | 输出数据到模板 | |
| <%- | 输出非转义数据到模板 | |
| <%# | 注释 | |
| <%% | 输出字符串『<%』 | |
| -%> | 结束时,删除换行符 | |
| _%> | 结束时,删除空格符 |
模板中采用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;
}
/* #################### 文章 #################### */