# Hexo 使用手册

# 1. Hexo 安装流程

# 1. 基础环境

  • 安装 NodeJs
  • 安装 hexo-cli : 全局安装 npm install hexo-cli -g

# 2. 初始化项目

  • 初始化项目文件夹 : hexo init blog
  • 安装 node 依赖: cd blog && npm install
  • 启动项目: hexo server -p port 指定本地启动端口,默认为 4000
  • 测试指令: hexo clean && hexo generate && hexo server , 简写: hexo clean && hexo g && hexo s

# 2. Hexo 主题

# 1. shoka 主题安装

主题功能基本介绍,请前往→優萌初華主页查看 || 本站内容

主题安装以及依赖处理,请前往→Reversesacle 主页查看

此篇文章主要解决 hexo 新版本使用 shoka 主题时的改动和兼容

hexo-theme-shoka主题安装流程

安装 Hexo

  • hexo-clinpm install hexo-cli -g --registry https://registry.npmmirror.com
  • 创建一个新目录,执行命令 hexo init
  • 切换到刚刚创建的新目录,将 ./_config.yml 里面内容清空,并添加下面的内容
# Site
title: Hexo
subtitle: 'Subtitle'
description: 'Description'
keywords:
author: Author Name
language: en
timezone: ''
# URL
## Set your site url here. For example, if you use GitHub Page, set url as 'https://username.github.io/project'
url: https://www.reversesacle.com
root: /
permalink: :title/ # edit for Theme.shoka
permalink_defaults:
pretty_urls:
  trailing_index: true # Set to false to remove trailing 'index.html' from permalinks
  trailing_html: true # Set to false to remove trailing '.html' from permalinks
# Directory
source_dir: source
public_dir: public
tag_dir: tags
archive_dir: archives
category_dir: categories
code_dir: downloads/code
i18n_dir: :lang
skip_render:
# Writing
new_post_name: :title.md # File name of new posts
default_layout: post
titlecase: false # Transform title into titlecase
external_link:
  enable: true # Open external links in new tab
  field: site # Apply to the whole site
  exclude: ''
filename_case: 0
render_drafts: false
post_asset_folder: false
relative_link: false
future: true
highlight:
  enable: false
  line_number: true
  auto_detect: false
  tab_replace: ''
  wrap: true
  hljs: false
prismjs:
  enable: false
  preprocess: true
  line_number: true
  tab_replace: ''
# Home page setting
# path: Root path for your blogs index page. (default = '')
# per_page: Posts displayed per page. (0 = disable pagination)
# order_by: Posts order. (Order by date descending by default)
index_generator:
  path: ''
  per_page: 5
  order_by: -date
  
# Category & Tag
default_category: uncategorized
category_map:
tag_map:
  
# Metadata elements
## https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta
meta_generator: true
# Date / Time format
## Hexo uses Moment.js to parse and display date
## You can customize the date format as defined in
## https://momentjs.com/docs/#/displaying/format/
date_format: YYYY-MM-DD
time_format: HH:mm:ss
## updated_option supports 'mtime', 'date', 'empty'
updated_option: 'mtime'
# Pagination
## Set per_page to 0 to disable pagination
per_page: 10
pagination_dir: page
# Include / Exclude file(s)
## include:/exclude: options only apply to the 'source/' folder
include:
exclude:
ignore:
# Extensions
## Plugins: https://hexo.io/plugins/
## Themes: https://hexo.io/themes/
theme: shoka
# Deployment
## Docs: https://hexo.io/docs/one-command-deployment
deploy:
  type: git
  repo: https://github.com/xxx/xxx.github.io.git   # 替换为你的仓库地址
  branch: main  # 或 master,取决于你的 GitHub 仓库设置

安装 hexo-theme-shoka 主题

  • 安装 hexo-theme-shoka 插件

    • git clone https://github.com/amehime/hexo-theme-shoka.git ./themes/shoka
  • 安装 hexo-renderer-multi-markdown-it 插件

    • npm un hexo-renderer-marked --save
    • npm install hexo-renderer-multi-markdown-it --ignore-scripts --save --registry https://registry.npmmirror.com
  • 安装 hexo-autoprefixer 插件

    • npm install hexo-autoprefixer --save --registry https://registry.npmmirror.com
      安装 hexo-symbols-count-time 插件
    • npm install hexo-symbols-count-time --save --registry https://registry.npmmirror.com
  • 安装 hexo-feed 插件

    • npm install hexo-generator-feed --save-dev --registry https://registry.npmmirror.com
  • 安装 hexo-algoliasearch 插件

    • npm install hexo-algoliasearch --save --registry https://registry.npmmirror.com
  • 安装 hexo-deployer-git 插件

    • npm install hexo-deployer-git --save --registry https://registry.npmmirror.com
  • 切换到项目的根目录,打开 ./_config.yml 文件,在文件末添加下面内容

markdown:
  render: # Render setting
    html: false # Filter HTML lable
    xhtmlOut: true # Use '/' to close the sigle lable(<br />)
    breaks: true # Change the '\n' in text to <br>
    linkify: true # Change the text form of link to real link
    typographer: 
    quotes: '“”‘’'
  plugins: # markdown-it plugins setting
    - plugin:
        name: markdown-it-toc-and-anchor
        enable: true
        options: # No need to change the below setting, it is default
          tocClassName: 'toc'
          anchorClassName: 'anchor'
    - plugin:
        name: markdown-it-multimd-table
        enable: true
        options:
          multiline: true
          rowspan: true
          headerless: true
    - plugin:
        name: ./markdown-it-furigana
        enable: true
        options:
          fallbackParens: "()"
    - plugin:
        name: ./markdown-it-spoiler
        enable: true
        options:
          title: "You Know too much"
  
# autoprefixer
autoprefixer:
  exclude:
    - '*.min.css'
# algolia
algolia:
  appId: # Your appId
  apiKey: # Your apiKey
  adminApiKey: # Your adminApiKey
  chunkSize: 5000
  indexName: #"shoka"
  fields:
    - title # default
    - path # default
    - categories # Optional setting
    - content:strip:truncate,0,2000
    - gallery
    - photos
    - tags
    
# feed
feed:
    limit: 20
    order_by: "-date"
    tag_dir: false
    category_dir: false
    rss:
        enable: true
        template: "themes/shoka/layout/_alternate/rss.ejs"
        output: "rss.xml"
    atom:
        enable: true
        template: "themes/shoka/layout/_alternate/atom.ejs"
        output: "atom.xml"
    jsonFeed:
        enable: true
        template: "themes/shoka/layout/_alternate/json.ejs"
        output: "feed.json"
  • 在项目根目录中打开 CMD , 执行 hexo clean && hexo g && hexo s 启动
  • 浏览器访问: http://localhost:4000

# 2. 插件处理

如果不想配置以下内容,可直接下载网盘文件,将文件解压后放到 ./themes/ 下,然后在 _config.yml 中修改 algoliawaline 相关配置即可

点击下载 (提取码: u7fi)

Algolia 搜索
  • 注册 Algolia 账号,官网地址

  • 官网完成注册后,进入 welcome 页面→点击 API keys,在 All API Keys 页面处点击 New API key 并填写相关信息

  • _config.yml 文件中添加对应配置信息

algolia:
   appId: "Application ID对应码"
   apiKey: "API Keys页面的All API Keys中新建的API key的对应码"
   adminApiKey: "Admin API Key对应码"
   chunkSize: 5000
   indexName: "项目索引名称, Indices部分"
   fields:
     - title 
     - path
     - categories
     - content: strip:truncate,0,4000
     - gallery
     - photos
     - tags
  • 生成索引信息:在 hexo 根目录右键点击 Git Bash Here ,输入 hexo clean && hexo g && hexo algolia 即可
  • (注意) 每当有新文章发布,都需输入 hexo g与hexo algolia 指令
Waline评论
  • 按照 Reversesacle 大佬的文档进行配置和修改

  • shoka 默认的评论插件 valine 由于兼容和 LeanCloud 不再提供项目部署服务等问题,以及 所以手动切换到 waline

  • waline 官方文档,按照文档步骤将后端服务部署到 Vercel 中,完成创建数据库和域名绑定

  • 修改文件: ./themes/shoka/_config.yml , 删除原有的 valine 配置并添加 waline 配置信息,

# 删除掉 valine 相关的所有内容,并添加下面的内容
# Comments
# For more information: https://github.com/amehime/MiniValine
waline:
  # Waline 的服务端地址,刚刚在 Vercel 记录的 DOMAINS 链接或自绑定的域名
  serverURL: 'https://xxx.vercel.app'
  comment: true   # 文章评论数统计,填入字符串时会作为 CSS 选择器
  pageview: true  # 文章浏览量统计,填入字符串时会作为 CSS 选择器
  lang: 'zh-CN'   # 多语言支持
  dark: 'auto' # 暗黑模式适配,true | false | 'auto'
  commentSorting: 'latest' # 评论列表排序方式。可选值: 'latest', 'oldest', 'hottest'
  login:  'disable' # 登录模式状态,'enable' | 'disable' | 'force'
  wordLimit: 0  # 评论字数限制。填入单个数字时为最大字数限制。设置为 0 时无限制
  pageSize: 10  # 评论列表分页,每页条数
  imageUploader: false  # 自定义图片上传方法。
  highlighter: true  # 代码高亮
  texRenderer: false #  自定义 tex 渲染
  search: false # 自定义搜索功能
  noCopyright: true # 是否隐藏页脚版权信息
  recaptchaV3Key: false # Google 提供的验证码服务
  turnstileKey: false # Cloudflare 提供的验证码服务
  reaction: false # 为文章增加表情互动功能
  meta: ['nick', 'mail', 'link']  # 评论者相关属性。可选值: 'nick', 'mail', 'link'
  requiredMeta: ['nick', 'mail'] # 设置必填项,默认匿名 ([])
  locale: {
    placeholder: "欢迎评论"
  } # https://waline.js.org/cookbook/customize/locale.html
  emoji: [
    https://unpkg.com/@waline/emojis@1.2.0/alus
  ] # 表情
  
# 滑动页面到最后面,找到 vendors 区域,按下面条件修改
comment: css/comment.css
修改为
waline_crop: css/comment.css
waline: combine/npm/@waline/client@3.1.3/dist/waline.min.css,npm/@waline/client@3.1.3/dist/waline-meta.min.css
valine: gh/amehime/MiniValine@4.2.2-beta10/dist/MiniValine.min.js
修改为
waline: npm/@waline/client@3.1.3/dist/waline.umd.min.js
  • 修改文件: ./themes/shoka/layout/_macro/comment.njk , 删除所有内容,添加下面的内容
// 由于 hexo 渲染识别问题,需要将下方内容中的 \% 替换为 \ 再添加到 comment.njk 文件中
{\% macro render() %}
{\%- if page.comment !== false %}
  <div class="wrap" id="comments">
    {\%- if theme.waline.serverURL %}
      <div id="waline-comment"></div>
    {\%- endif %}
  </div>
{\%- endif %}
{\% endmacro %}
  • 修改文件: ./themes/shoka/layout/_macro/widgets.njk 28 行左右 <ul class="leancloud-recent-comment"></ul> ==> <ul id="waline-recent"></ul>

  • 修改文件: ./themes/shoka/layout/_partials/layout.njk 111 行左右 valine: {{ page.valine|safedump if page.valine else "true" }},{%- endif %} ==> waline: {{ page.waline|safedump if page.waline else "true" }},{%- endif %}

  • 修改文件: ./themes/shoka/layout/_partials/post/footer.njk 第 13 行左,将 {%- if ~ 到 {%- endif %} 区域,更改为下面内容:

    • {%- if theme.valine.appId and theme.valine.appKey and theme.valine.visitor %} ==> {%- if theme.waline.serverURL and theme.waline.pageview %}
    • <span id= 标签修改为 <span class="item" data-path="{{ '/' + (page.path | replace('index.html', '')) }}" title="{{ __('post.views') }}">
    • <span class="leancloud-visitors-count"></span> ==> <span class="waline-pageview-count"></span>
  • 修改文件: ./themes/shoka/scripts/generaters/script.js , 第 23 行到 38 行左右,修改为下面的内容

js: {
  waline: theme.vendors.js.waline,
  chart: theme.vendors.js.chart,
  copy_tex: theme.vendors.js.copy_tex,
  fancybox: theme.vendors.js.fancybox
},
css: {
  waline_crop: theme.css + "/comment.css",
  waline: theme.vendors.css.waline,
  katex: theme.vendors.css.katex,
  mermaid: theme.css + "/mermaid.css",
  fancybox: theme.vendors.css.fancybox
},
loader: theme.loader,
search : null,
waline: theme.waline,
quicklink: {
  timeout : theme.quicklink.timeout,
  priority: theme.quicklink.priority
}
  • 修改文件: ./themes/shoka/source/css/comment.styl 修改第 5 行为下面内容
@import "_common/components/third-party/waline";
  • 修改文件: ./themes/shoka/source/js/_app/utils.js 第 45 行左右的 vendorCSS 函数后添加下面内容
const vendorCss_body = function(type) {
  var name = 'css-' + type;
  if(undefined != document.querySelector('body link' + '.' + name)){
    return;
  }
    
  var new_link = document.body.createChild('link', {
    rel: 'stylesheet',
    href: assetUrl("css", type)
  });
  new_link.setAttribute('class',name);
};
  • 修改文件: ./themes/shoka/source/js/_app/page.js 第 408 行左右的 loadComments 函数,修改函数内部的 vendorCss('valine'); ,更改为下面内容
// 有两处都需要修改
vendorCss_body('waline_crop');
  • 修改文件: ./themes/shoka/source/js/_app/pjax.js
// 第 49 行左右的 `siteRefresh` 函数,在其前面添加下面的两个函数
const recent_comment_create = function(comments) {
  var waline_recent = $('#waline-recent');
  if(waline_recent.hasChildNodes())
  {
    var parent_label = waline_recent.parentNode;
    parent_label.removeChild(waline_recent);
    parent_label.appendChild(waline_recent.cloneNode(false));
  }
  var len = comments.length;
  if(0 == len){ return; }
  var getDate = function(timestamp) {
    var date = new Date(timestamp);
    var year = date.getFullYear();
    var month = date.getMonth() + 1;
    var day = date.getDate();
    month = month < 10 ? '0' + month : month;
    day = day < 10 ? '0' + day : day;
    return year + '-' + month + '-' + day;
  };
  var getText = function(new_text) {
    new_text = new_text.replace(/\n/g, '');
    var place_text = '';
    var text_size = 100;
    var len = new_text.length;
    var smallest = [
      'r','g','t','j',
      'f','i','v','y',
      'x','z',',','.',
      '!',';',':','"',
      '(',')','-',' '
    ];
    var regex = /[a-z]/;
    var regex2 = /[!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]/;
    var regex3 = /[A-Z]/;
    for(var i = 0,count = 0;i < len;)
    {
      if('<' == new_text[i])
      { 
        while(i < len && '>' != new_text[i++]);
        continue;
      }
      if(count >= text_size)
      {
        if(i < len){ place_text += '...'; }
        break; 
      }
      var c = new_text[i++];
      if(c in smallest){ count += 1; }
      else if(regex.test(c) || regex2.test(c)){ count += 2; }
      else if(regex3.test(c)){ count += 2.5; }
      else{ count += 3.5; }
      
      place_text += c;
    }
  
    return place_text;
  };
  var i = 0;
  var li_label = document.createElement('li');
  li_label.setAttribute('class','item');
  
  var a_label = document.createElement('a');
  a_label.setAttribute('href',comments[i].url);
  a_label.setAttribute('data-pjax-state','');
  var breadcrumb_label = document.createElement('span');
  breadcrumb_label.style.marginBottom = '-5.3px';
  breadcrumb_label.innerText = comments[i].nick + ' @ ' + getDate(comments[i].time);
  var text_span = document.createElement('span');
  text_span.innerText = getText(comments[i].comment);
  waline_recent = $('#waline-recent');
  a_label.appendChild(breadcrumb_label);
  a_label.appendChild(text_span);
  li_label.appendChild(a_label);
  waline_recent.append(li_label);
  for(++i;i < len;++i)
  {
    var new_li_label = li_label.cloneNode(false);
    var new_a_label = a_label.cloneNode(false);
    new_a_label.setAttribute('href',comments[i].url);
    var new_breadcrumb_label = breadcrumb_label.cloneNode(false);
    new_breadcrumb_label.innerText = comments[i].nick + ' @ ' + getDate(comments[i].time);
    var new_text_span = text_span.cloneNode(false);
    new_text_span.innerText = getText(comments[i].comment);
    new_a_label.appendChild(new_breadcrumb_label);
    new_a_label.appendChild(new_text_span);
    new_li_label.appendChild(new_a_label);
    waline_recent.append(new_li_label);
  }
};
const waline_create = function() {
  if(CONFIG.waline.serverURL)
  {
    vendorCss_body('waline');
    var getScript = function(options) {
      var name = 'script-waline';
      var old_script = document.querySelector('body script' + '.' + name);
      if(old_script){ document.body.removeChild(old_script); }
      var script = document.createElement('script');
      script.defer = true;
      script.crossOrigin = 'anonymous';
      script.setAttribute('class','script-waline');
      Object.keys(options).forEach(function(key) {
          script[key] = options[key];
      });
      document.body.appendChild(script);
    };
    getScript({
      src: assetUrl('js','waline'),
      onload: function() {
        var options = Object.assign({}, CONFIG.waline);
        options = Object.assign(options, LOCAL.waline||{});
        Waline.RecentComments({
          serverURL: options.serverURL,
          count: 10,
        }).then(({ comments }) => { recent_comment_create(comments.data); });
        if($('#waline-comment'))
        {
          options.el = '#waline-comment';
          Waline.init(options);
          setTimeout(function(){
            positionInit(1);
            postFancybox('#waline-comment');
          }, 1000);
        }
      }
    });
  }
};
// 之后将 `siteRefresh` 函数中
// 第 58 行到 72 行左右的 `vendorJs ('valine',function (){}` 部分删除,
// 并添加下面内容
waline_create()
  • 删除文件: ./themes/shoka/source/css/_common/components/third-party/valine.styl
  • 创建新文件: ./themes/shoka/source/css/_common/components/third-party/waline.styl , 将下面内容复制到文件中并保存
:root {
    --waline-border-color: initial;
    --waline-border: initial;
    --waline-font-size: initial;
}
[data-theme="dark"]:root {
    --waline-info-bg-color: var(--grey-3);
}
[data-waline] {
    font-size: unset;
    text-align: left;
}
[data-waline] * {
    box-sizing: unset;
    line-height: unset;
}
[data-waline] p {
    color: unset;
}
[data-waline] pre {
    overflow: unset;
    padding: unset;
    line-height: unset;
    margin: unset;
    padding: unset;
    border-radius: unset;
    background: unset;
    font-size: unset;
}
[data-waline] code {
    padding: .1em .2em;
}
[data-waline] pre code {
    padding: unset;
    background: unset;
    color: unset;
    white-space: unset;
    word-break: unset;
}
#comments {
    animation: none;
    opacity: 0;
}
#waline-comment
{
    .wl-comment .wl-footer .upload,
    .wl-comment .wl-footer .wl-gif-popup,
    .wl-comment .wl-footer .wl-text-number,
    .wl-meta-head .wl-sort,
    .wl-cards .wl-like,
    .wl-cards .wl-addr {
        display: none;
    }
    input,
    textarea {
        border: none;
        outline: none;
        background: transparent;
        font-size: $font-size-smallest;
        transition: all .25s ease;
    }
    a {
        color: var(--color-aqua);
        border-bottom: initial;
        padding-bottom: initial;
        &:hover {
            color: var(--primary-color);
        }
    }
    li {
        list-style: none;
    }
    .text-center {
        text-align: center;
    }
    .text-right {
        text-align: right;
    }
    .float-right {
        float: right !important;
    }
    .pd5 {
        padding: 5px;
    }
    .pd10 {
        padding: 10px;
    }
    .wl-comment {
        shadow-box();
        margin-bottom: .625rem;
        position: relative;
        padding: .625rem;
        .wl-panel {
            background: initial;
            margin: unset;
        }
        .wl-header {
            display: flex;
            padding: .3em .6em;
            .wl-header-item {
                flex: 1 1 27%;
                width: 27%;
                input {
                    padding: .625rem .3125rem;
                    width: 100%;
                    border-bottom: .0625rem dashed var(--grey-4);
                    color: var(--text-color);
                    &:focus {
                        border-bottom-color: var(--primary-color);
                    }
                }
                label {
                    position: absolute;
                    margin-left: -10px;
                    color: var(--grey-6);
                }
                
                .wl-nick,
                .wl-mail {
                    padding-left: 35px;
                }
                .wl-link {
                    padding-left: 60px;
                }
            }
        }
        .wl-editor {
            width: 97%;
            height: 123px;
            min-height: 8.75em;
            line-height: 1.75;
            resize: vertical;
            margin-left: 10px;
        }
        .wl-footer {
            padding: .625rem 0;
            display: flex;
            .wl-action {
                color: var(--grey-6);
                transition: all .25s ease;
                &:first-child {
                    line-height: 1.3;
                }
            }
            .wl-btn {
                display: inline-block;
                cursor: pointer;
                touch-action: manipulation;
                text-align: center;
                text-decoration: none;
                vertical-align: middle;
                white-space: nowrap;
                border-radius: 0.3rem;
                border: 0.0625rem solid var(--grey-3);
                color: var(--grey-6);
                font-size: $font-size-small;
                font-weight: 400;
                line-height: 1.5;
                background: 0 0;
                margin-bottom: 0;
                min-height: 1em;
                padding: 0.5em 1.25em;
                -webkit-user-select: none;
                -moz-user-select: none;
                -ms-user-select: none;
                user-select: none;
                outline: 0;
                will-change: auto;
                transition: all .2s ease-in-out 0s;
                &:hover {
                    background: var(--primary-color);
                    color: var(--grey-0);
                }
            }
        }
        .wl-actions {
            margin-bottom: -20px;
            .wl-action {
                font-size: $font-size-small;
                &:first-child svg {
                    margin-bottom: -7px;
                }
                &:last-child {
                    float: right;
                }
                &:hover {
                    color: var(--primary-color);
                }
            }
        }
    }
    .wl-cards {
        .wl-meta {
            margin-top: -5px;
            margin-left: -1px;
            >span {
                font-size: $font-size-smallest;
                padding: 0.5px 2px;
                line-height: 1.15;
                padding-top: 0;
            }
        }
        .wl-comment-actions button {
            &:hover {
                color: var(--primary-color);
            }
        }
        .wl-reply-wrapper .wl-close {
            .wl-close-icon {
                width: 40px;
                height: 40px;
                color: var(--grey-1);
            }
        }
        .wl-quote {
            border-bottom: 0.0625rem dashed var(--grey-3);
            .wl-card {
                border-bottom: 0.0625rem dashed var(--grey-3);
            }
            div:last-child .wl-card {
                border-bottom: none;
            }
        }
    }
    .wl-meta>.wl-browser::before,
    .wl-meta>.wl-os::before {
        margin-bottom: -0.8px;
    }
    .wl-content pre, 
    .wl-content pre[class*=language-] {
        overflow: unset;
        margin: unset;
        padding: unset;
        border-radius: unset;
        background: unset;
        line-height: unset;
    }
    .wl-content pre code, 
    .wl-content pre[class*=language-] code {
        padding: unset;
        border-radius: unset;
        background: unset;
        color: unset;
        direction: unset;
    }
    .wl-content pre {
        position: relative;
        overflow: hidden;
        border-radius: 0.5rem;
        box-shadow: 0 0.3125rem 0.625rem -0.125rem var(--grey-9-a1);
        background: var(--grey-2);
        color: var(--grey-7);
        line-height: 1.6;
        margin: 1.25rem auto;
        display: block;
        margin-block-start: 1em;
        margin-block-end: 1em;
        margin-inline-end: 40px;
        white-space: pre;
        border-spacing: 0;
        width: 100%;
        border-collapse: separate;
        text-indent: initial;
        overflow-x: scroll;
        overflow-y: hidden;
        code {
            vertical-align: middle;
            border-color: inherit;
        }
        span {
            margin-left: 5px;
        }
    }
    .wl-loading {
        
    }
}
#waline-recent {
    .item {
        counter-increment: counter-post;
        &::before{
            content: counter(counter-post);
        }
    }
}
.waline-pageview-count {
    margin-left: 3px;
}

# 3. 页面配置

标签页标题
  • 文件位置: ./themes/shoka/languages
# show 浏览页为当前页,hide 浏览页为其他页面所展示的内容
favicon:
  show: (*^▽^*)
  hide: o(╥﹏╥)o

页内 (show): (●´3`●)やれやれだぜ

页外 (hide): (´Д`)大変だ!

顶部导航
  • 配置文件: ./themes/shoka/_config.yml 文件中的 menu 元素
  • 中文映射: ./themes/shoka/languages 目录中对应语言文件中的 menu 元素
#./themes/shoka/_config.yml
menu:
  home: / || home
  about: /about/ || user
  posts:
     default: / || feather
     archives: /archives/ || list-alt
     categories: /categories/ || th
     tags: /tags/ || tags
  friend-links: /friend-links/ || heart
  books:
     default: / || clipboard
     deeplearning-books: /books/deeplearning-books/ || fedora
     computer-books: /books/computer-books/ || opera
     phone-books: /books/phone-books/ || android
#./themes/shoka/language/zh-CN.yml (简中)
menu:
  home: 首页
  posts: 文章
  archives: 归档
  categories: 分类
  tags: 标签
  about: 关于
  search: 搜索
  friend-links: 友链
  books: 书架
  deeplearning-books: 人工智能
  computer-books: PC端技能
  phone-books: MD端技能
加载动画
  • CSS 样式文件: ./themes/shoka/source/css/_common/components/third-party/loading.styl
// 保留下面这几行,其余删除后写入新样式 
#loading {
  @extend $fix-fullscreen;
  background-color: var(--grey-1);
  if(!hexo-config('loader.start')) {
    display: none;
  }
}
// 填入自定义的CSS样式代码
  • HTML 文件: ./themes/shoka/layout/_partials/layout.njk
<body itemscope itemtype="https://schema.org/WebPage">
    <div id="loading">
    
    </div>
    <div id="container">

# 3. Hexo 常用命令

  1. 新建文章
hexo new "文章名称"	# windows
$ sudo hexo new "文章名称"  # linux
  1. 删除文章
# 删除项目根目录下的 db.json 文件
# 删除单篇文章:搜索对应文章标题 将 title... 相关文件和.html 文件一并删除
# 依次执行 hexo clean, hexo g, hexo d 即可
  1. 清理缓存

    • 清理缓存文件 db.json 和 已生成的静态文件 public

    • 对站点的修改配置不生效时,执行以下命令

hexo clean			# windows
$ sudo hexo clean   # linux
  1. 生成静态文件
hexo generate			# windows
$ sudo hexo clean  		# linux
hexo g					# 简写
  1. 生成索引
# 前置条件:安装 hexo-algolia 插件
npm install --save hexo-algolia
# 将索引信息上传到 Algolia 中的对应 index
hexo algolia
  1. 启动服务器
hexo server			 # windows
$ sudo hexo server   # linux
hexo s				 # 简写
  1. 部署网站
# 前置条件:安装 hexo-deployer-git 依赖
npm install hexo-deployer-git --save --registry https://registry.npmmirror.com
# 在_config.yml 文件中添加如下配置
deploy:
  type: git
  repo: https://github.com/xxx/xxx.github.io.git # 替换为自己的仓库地址
  branch: main  # 仓库分支
# 生成静态文件,部署到远程仓库
hexo deploy			 # windows
$ sudo hexo deploy   # linux
hexo d				 # 简写

# 4. MarkDown 文章配置

# 1. front-matter 标签

# 文章顶部区域 hexo 内置标签属性
‌title‌: 文章的标题。
‌date‌: 文章的发布日期,格式通常是YYYY-MM-DD。
‌tags‌: 文章的标签,可以是一个或多个。
‌categories‌: 文章的分类,同样可以是一个或多个。
‌updated‌: 文章的最后更新日期,格式与date相同。如果不指定,date字段的值会被用作updated。
‌comments‌: 是否开启评论,通常设置为true或false。
‌permalink‌: 自定义文章的永久链接。
‌photos‌: 用于展示图片列表的字段,常用于带有图片集的文章。
‌mathjax‌: 启用MathJax支持数学公式的渲染,设置为true。
‌description‌: 文章描述,用于SEO优化。
‌keywords‌: 文章关键词,用于SEO优化。
‌sticky‌: 文章是否置顶, 多文章时时间逆序排序
‌layout‌: post | page | draft
# 案例
title: Hexo-Shoka主题功能介绍补充点 #文章名字
date: 2021-11-28 01:04:31	#日期
tags: 主题		 #文章内容的标签,单个标签
#tags:	# 多个标签
# - tag1
# - tag2
#cover: https://../2021.jpg 	 #封面图片
#sticky: true			# 是否置顶
#type: "about"			# 当新建一个页面时需配置此参数
#categories: [xxx,xxx]  	# 该文件路径
#keywords: "文章内容的描述"
#disableNunjucks: true	# 启用时禁用 Nunjucks 标签
  1. 核心内置标签
标签名 类型 说明 示例
title String 文章标题。如果不填,默认使用文件名。 title: 我的第一篇博客
date Date 创建时间。格式: YYYY-MM-DD HH:mm:ss date: 2026-03-20 10:00:00
updated Date 更新时间。用于显示 “最后修改时间”。 updated: 2026-03-21 15:30:00
tags Array 标签。可以是单个字符串或数组。 tags: [Hexo, 教程]tags: Hexo
categories Array 分类。支持多级分类(嵌套数组)。 categories: [技术, Hexo]
layout String 布局模板。指定使用哪个 .njk/.ejs 文件渲染。默认是 postpage layout: photo (需主题支持)
permalink String 永久链接。覆盖全局配置,自定义该文章的 URL。 permalink: my-custom-url
comments Boolean 是否开启评论。 false 可关闭该文章的评论区。 comments: false
toc Boolean 是否显示目录。部分主题支持,用于控制文章内目录。 toc: true
draft Boolean 是否为草稿。 true 时执行 hexo s 可见,但 hexo g 不会生成。 draft: true
photos Array 相册图片。主要用于 photo 布局,展示图片墙。 photos: [img1.jpg, img2.jpg]
  1. 常见主题扩展标签 (Theme Specific)
标签名 说明 示例
cover / banner 文章顶部封面图路径。 cover: /img/cover.jpg
top / sticky 置顶文章。数字越大越靠前。 top: 10
copyright 自定义该文章的版权协议(覆盖全局)。 copyright: cc_by_nc_sa
mathjax / math 强制在该文章开启数学公式渲染。 mathjax: true
mermaid 开启 Mermaid 流程图支持。 mermaid: true
aside 控制侧边栏显示 ( true / false )。 aside: false
highlight_theme 单独设置该文章的代码高亮主题。 highlight_theme: mac-light
ai摘要 (新) 部分新主题支持 AI 生成的摘要字段。 ai_summary: "这是AI生成的摘要..."
series 文章系列名称,用于生成系列导航。 series: Hexo 入门教程
series_index 文章在系列中的序号。 series_index: 3
reward 是否开启打赏 (true/false)
edit_link 自定义文章的 "编辑此页" 链接
  1. 插件扩展标签 (Plugin Specific)
插件名 新增标签 说明
hexo-abbrlink abbrLink 自定义短链接哈希值。
hexo-generator-searchdb (无) 通常自动抓取,无需配置。
hexo-related-popular-posts popularPosts 控制相关文章显示。
hexo-filter-nofollow nofollow 控制外部链接属性。
hexo-generator-ai-summary ai_summary 存储 AI 生成的摘要内容。

# 2. 常用语法标签

点击查看

以下介绍可以使用的 markdown 或者标签代码。
功能大部分基于 hexo-renderer-multi-markdown-it 渲染器,尤其是代码块的显示,与默认渲染器不兼容。

# 5. 常见问题合集

# 1. hexo-symbols-count-time 依赖

hexo 8.1.1 中一些内置对象的属性结构出现变更,导致插件无法正确得到结果,模板中渲染结果会变成 NANM || NAN

  1. 文章总字数统计 symbolsCountTotal(site)
// 源码部分
function getSymbols(post) {
  return post.length;
};
function getSymbolsTotal(site) {
  let symbolsResultCount = 0;
  site.posts.forEach(post => {
    symbolsResultCount += getSymbols(post);
  });
  return symbolsResultCount;
};
module.exports.symbolsCountTotal = function(site) {
  let symbolsResultTotal = getSymbolsTotal(site);
  return symbolsResultTotal < 1000000
    ? Math.round(symbolsResultTotal / 1000) + 'k' // < 999k => 111k
    : Math.round(symbolsResultTotal / 100000) / 10 + 'm'; // > 999k => 1.1m
};
  1. 阅读所需时长 symbolsTimeTotal(site, awl, wpm, suffix)
// 源码部分
module.exports.symbolsTimeTotal = function(site, awl = config.awl, wpm = config.wpm, suffix = config.suffix) {
  let minutes = Math.round(getSymbolsTotal(site) / (awl * wpm));
  return getFormatTime(minutes, suffix);
};
  1. 异常原因

    • hexo 新版本的 site.posts 对象结构发生变化
    posts:{
        data:[Document],
        length: number,
    }
    data: {
        _content: '', // 文章内容
        path: [Getter],
        permalink: [Getter],
        full_source: [Getter],
        asset_dir: [Getter],
        tags: [Getter],
        categories: [Getter],
        prev: [Circular *3],
        next: [_Document],
        __post: true
    }
  2. 解决方法

  • 修正 \项目主文件\node_modules\hexo-symbols-count-time\lib\helper.js 中对应的 getSymbols 以及 getSymbolsTotal 方法中的异常属性
// 修正后的结果
function getSymbols(post) {
  return post._content.length;
};
function getSymbolsTotal(site) {
  let symbolsResultCount = 0;
  site.posts.data.forEach(post => {
    symbolsResultCount += getSymbols(post);
  });
  return symbolsResultCount;
};

# 2. code 代码块儿渲染异常

  1. 异常原因: hexo 新版本的默认高亮渲染器禁用方法变更,导致主题的自定义渲染器没有正确生效
  2. 解决方法:
# 旧版本禁用方式
highlight:
  enable: false
prismjs:
  enable: false
  
# 新版本变更
syntax_highlighter: false
prismjs:
  enable: false