# 环境准备

# 安装 Node.js

  • 直接到官网上下载安装即可 https://nodejs.org/en/download/

    • Node.js (Node.js 版本需不低于 10.13,建议使用 Node.js 12.0 及以上版本)

    • Node 自带 npm

  • npm 换源

npm config set registry https://registry.npmmirror.com	淘宝
npm config set registry https://npm.aliyun.com	阿里云
npm config set registry http://mirrors.cloud.tencent.com/npm/	腾讯云
npm config set registry https://mirrors.huaweicloud.com/repository/npm/	华为云

# 安装 Git

  • Windows:下载并安装 git.

# 安装 Hexo

  1. 安装 hexo
npm install -g hexo-cli
  1. 输入 hexo -v 查看版本
  1. 初始化 hexo,新建存储博客的文件夹
hexo init myblog
  1. 进入文件夹,安装 npm
cd myblog
npm install
  1. hexo 安装成功
  1. 启动服务站点
hexo g  或  hexo generate
hexo s  或  hexo server
  1. 本地访问:http://localhost:4000/

# GitHub 上建站访问

# 新建仓库

  • 仓库名称限制为:用户名 +.github.io

# 安装 hexo 上传插件

npm install hexo-deployer-git --save

# 修改 hexo 配置文件指定仓库路径

  • 修改 _config.yml ,找到 # Deploymentdeploy
deploy:
  type: git  
  repo: 你的github仓库路径  
  branch: master

# 推送站点到 github

hexo d
  • 推送过程中需要输入你的 github 用户名和密码,密码需要用官方的 token 或者采用 ssh 公私钥访问。
  • 解决方式:
    • 创建一个新 token,把它当密码输入即可
      • setting->Developer Settings->Prosonal access tokens
    • 采用 ssh 公私钥访问
  • 上传成功

# 输入网址访问

# 更换主题

本文使用的主题是:shako

# 下载主题并配置

cd myblog
git clone https://github.com/amehime/hexo-theme-shoka.git ./themes/shoka
  • 下载完成后,文件会出现在 theme 目录下
  • 编辑博客根目录下的 _config.yml , 搜索 theme ,修改为:
theme: shoka
  • 删除 _config.landscape.yml ,这是 hexo 默认主题的配置文件,建议删除

# 切换语言包

  • 编辑博客根目录下的 _config.yml , 搜索 language
language: zh-CN

# 安装插件

# hexo-renderer-multi-markdown-it

  1. 卸载掉默认的 hexo-renderer-marked ,以及别的 markdown 文件渲染器。
npm un hexo-renderer-marked --save
  1. 在博客根目录下用如下命令进行插件安装
npm i hexo-renderer-multi-markdown-it --save
  1. 编辑 _config.yml , 配置插件参数,在合适位置添加
markdown:
  render: # 渲染器设置
    html: true # 过滤 HTML 标签
    xhtmlOut: true # 使用 '/' 来闭合单标签 (比如 <br />)。
    breaks: true # 转换段落里的 '\n' 到 <br>。
    linkify: true # 将类似 URL 的文本自动转换为链接。
    typographer: 
    quotes: '“”‘’'
  plugins: # markdown-it 插件设置
    - plugin:
        name: markdown-it-toc-and-anchor
        enable: true
        options: # 文章目录以及锚点应用的 class 名称,shoka 主题必须设置成这样
          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: "你知道得太多了"
  1. 继续编辑 _config.yml , 找到如下字段
highlight:
  enable: true
  line_number: true
  auto_detect: false
  tab_replace: ''
  wrap: true
  hljs: false

enable: true 改成 enable: false

# hexo-autoprefixer

  1. 安装
npm install hexo-autoprefixer --save
  1. 编辑 _config.yml ,合适位置加入
autoprefixer:
  exclude:
    - '*.min.css'

# hexo-lightning-minify

  1. 安装
npm install hexo-lightning-minify
  1. 编辑 _config.yml ,合适位置加入
minify:
  js:
    enable: false # ShokaX 自带 esbuild 优化,不建议开启,其他主题建议开启
    exclude: # 排除文件,接受 string [],需符合 micromatch 格式
  css:
    enable: true # 开启 CSS 优化
    options:
      targets: ">= 0.5%" # browserslist 格式的 target
    exclude: # 排除文件,接受 string [],需符合 micromatch 格式
  html:
    minifier: html-minifier
    enable: true # 开启 HTML 优化
    options:
      comments: false # 是否保留注释内容
    exclude: # 排除文件,接受 string [],需符合 micromatch 格式
  image:
    enable: true # 开启图片预处理和自动 WebP 化
    options:
      avif: false
      webp: true # 预留配置项,现版本无作用
      quality: 80 # 质量,支持 1-100 的整数、lossless 或 nearLossless
      effort: 2 # CPU 工作量,0-6 之间的整数 (越低越快)
      replaceSrc: true # 自动替换生成 html 中的本地图片链接为 webp 链接
      # 我们更建议使用 Service Worker 来在用户侧实现 replaceSrc 的功能,这将能够以一种侵入式更小的方式实现链接替换
    exclude:
  1. 修改 hexo-lightning-minify/lib/img.js ,这样转换格式之后会覆盖原图片
//transformImage 函数
if (!(await promises_1.default.access(webpImagePath).catch(() => false))) {
    await (0, sharp_1.default)(sourceImagePath)
        .webp(sharpOptions)
        .toFile(webpImagePath)
        .then(info => {
            this.log.info(`Converted ${imagePath} to WebP (${info.size} bytes)`);
        	// 添加如下内容
        	// ----------
            // 检查转换后的图片是否成功写入文件系统
            if (info.size > 0) {
                // 如果成功,删除原始图片
                return promises_1.default.unlink(sourceImagePath);
            }
        	// ----------
        })
        .catch((err) => {
            if (err.toString().indexOf('Input file is missing') !== -1) {
                firstRun = true;
            }
            else {
                this.log.error(`Error converting ${imagePath} to WebP:`, err);
            }
        });
}

# hexo-asset-image

在 Hexo 中引入本地图片而不用图床

  1. 安装
npm install https://github.com/xcodebuild/hexo-asset-image.git --save
  1. 修改项目根目录下的 _config.yml 文件参数 post_asset_folder 值为 true
# 开始使用本地静态资源
post_asset_folder: true
  1. 完成上述配置后,在使用命令 hexo new post 新建文章时,将会在 source/_posts 目录下创建一个与文章同名的目录。
hexo new post "测试文章"
|____scaffolds
|____source
| |_____posts
| | |____测试文章.md
| | |____测试文章 # 与文章同名的目录,用于保存在文章中引入的本地图片资源
|____themes
  1. 引用

Hexo 上 markdown 图片路径与 Typora 保持一致

  1. 安装
npm install hexo-image-link --save
  1. 打开 Hexo 资源管理配置开关
# _config.yml
post_asset_folder: true

# hexo-symbols-count-time

统计页面或者站点的单词以及阅读所需时间

  1. 安装
npm install hexo-symbols-count-time
  1. 编辑 _config.yml ,修改以下内容
footer:
  # Specify the date when the site was setup. If not defined, current year will be used.
  since: 2010
  icon:
    name: sakura rotate
    # Change the color of icon, using Hex Code.
    color: "#ffc0cb"
  # Dependencies: https://github.com/theme-next/hexo-symbols-count-time
  count: true
  powered: true
post:
  # Dependencies: https://github.com/theme-next/hexo-symbols-count-time
  count: true

# hexo-generator-searchdb

实现本地搜索

  1. 安装插件
npm install hexo-generator-searchdb
  1. 修改 page.js

将整个 localSearch 复制到主题的 shoka/source/js/_app/page.js

const localSearch = function(pjax) {
  // 参考 hexo next 主题的配置方法
  // 参考 https://qiuyiwu.github.io/2019/01/25/Hexo-LocalSearch/ 博文
  if(CONFIG.localSearch === null)
    return
  if(!siteSearch) {
    siteSearch = BODY.createChild('div', {
      id: 'search',
      innerHTML: '<div class="inner"><div class="header"><span class="icon"><i class="ic i-search"></i></span><div class="search-input-container"><input class="search-input"autocompvare="off"placeholder="'+LOCAL.search.placeholder+'"spellcheck="false"type="text"id="local-search-input"></div><span class="close-btn"><i class="ic i-times-circle"></i></span></div><div class="results"id="search-results"><div class="inner"><div id="search-stats"></div><div id="search-hits"></div><div id="search-pagination"></div></div></div></div></div>'
    });
  }
  
  var isFetched = false;
  var datas;
  var isXml = true;
  var current_page = 0;
  var pageSize = parseInt(CONFIG.localSearch.pageSize, 10);
  if(isNaN(pageSize)) pageSize = 10;
  var total_pages = 0;
  var max_page_on_show = 7; // 一次最多显示 7 个页码
  var start_page = 0;
  var end_page = 0;
  var resultItems = [];
  // search DB path
  var searchPath = CONFIG.localSearch.path;
  if (searchPath.length == 0) {
    searchPath = 'search.xml';
  } else if (searchPath.endsWith('json')) {
    isXml = false;
  }
  const input = $('.search-input'); // document.querySelector('.search-input');
  const resultContent = document.getElementById('search-hits');
  const paginationContent = document.getElementById('search-pagination');
  const getIndexByWord = function(word, text, caseSensitive) {
    if (CONFIG.localSearch.unescape) {
      var div = document.createElement('div');
      div.innerText = word;
      word = div.innerHTML;
    }
    var wordLen = word.length;
    if (wordLen === 0) {
      return [];
    }
    var startPosition = 0;
    var position = [];
    var index = [];
    if (!caseSensitive) {
      text = text.toLowerCase();
      word = word.toLowerCase();
    }
    while ((position = text.indexOf(word, startPosition)) > -1) {
      index.push({position:position, word:word});
      startPosition = position + wordLen;
    }
    return index;
  };
  // Merge hits into slices
  const mergeIntoSlice = function(start, end, index, searchText) {
    var item = index[index.length - 1];
    var position = item.position;
    var word = item.word;
    var hits = [];
    var searchTextCountInSlice = 0;
    while (position + word.length <= end && index.length !== 0) {
      if (word === searchText) {
        searchTextCountInSlice++;
      }
      hits.push({
        position:position,
        length: word.length
      });
      var wordEnd = position + word.length;
      // Move to next position of hit
      index.pop();
      while (index.length !== 0) {
        item = index[index.length - 1];
        position = item.position;
        word = item.word;
        if (wordEnd > position) {
          index.pop();
        } else {
          break;
        }
      }
    }
    return {
      hits:hits,
      start:start,
      end:end,
      searchTextCount: searchTextCountInSlice
    };
  }
  // Highlight title and content
  const highlightKeyword = function(text, slice) {
    var result = '';
    var prevEnd = slice.start;
    slice.hits.forEach(function(hit)  {
      result += text.substring(prevEnd, hit.position);
      var end = hit.position + hit.length;
      result += '<mark>'+ text.substring(hit.position, end)+'</mark>';
      prevEnd = end;
    });
    result += text.substring(prevEnd, slice.end);
    return result;
  };
  const pagination = function() {
    
    const addPrevPage = function(current_page) {
      var classContent = '';
      var numberContent = '';
      if (current_page === 0) {
        classContent = '#search-pagination pagination-item disabled-item';
        numberContent = '<span class="#search-pagination page-number"><i class="ic i-angle-left"></i></span>';
      } else {
        classContent = '#search-pagination pagination-item';
        numberContent = '<a class="#search-pagination page-number" aria-label="Prev" href="#"><i class="ic i-angle-left"></i></a>';
      }
      var prevPage = '<li class="'+ classContent +'" id="prev-page">'+ numberContent+'</li>';
      return prevPage;
    };
    const addNextPage = function(current_page) {
      var classContent = '';
      var numberContent = '';
      if ((current_page + 1) === total_pages) {
        classContent = '#search-pagination pagination-item disabled-item';
        numberContent = '<span class="#search-pagination page-number"><i class="ic i-angle-right"></i></span>';
      } else {
        classContent = '#search-pagination pagination-item';
        numberContent = '<a class="#search-pagination page-number"aria-label="Next"href="#"><i class="ic i-angle-right"></i></a>';
      }
      var nextPage = '<li class="' + classContent +'"id="next-page">'+ numberContent +'</li>';
      return nextPage;
    };
    const addPage = function(index, current_page)  {
      var classContent = '';
      var numberContent = '<a class="#search-pagination page-number"aria-label="'+ (index + 1) +'"href="#">'+(index+1)+'</a>';
      if (index === current_page) {
        classContent = '#search-pagination pagination-item current';
      } else {
        classContent = '#search-pagination pagination-item';
      }
      var page = '<li class="'+classContent+'" id="page-'+(index + 1)+'">'+numberContent+'</li>';
      return page;
    }
    
    const addPaginationEvents = function(start_page, end_page)  {
      if (total_pages <= 0) {
        return;
      }
      const onPrevPageClick = function(event) {
        if (current_page > 0) {
          current_page -= 1;
        }
        if (current_page < start_page) {
          start_page = current_page;
          end_page = Math.min(end_page, start_page + max_page_on_show);
        }
        pagination();
      };
      const onNextPageClick = function(event)  {
        if ((current_page + 1) < total_pages) {
          current_page += 1;
        }
        if (current_page > end_page) {
          end_page = current_page;
          start_page = Math.max(0, end_page - max_page_on_show);
        }
        pagination();
      };
      const onPageClick = function(event)  {
        var page_number = parseInt(event.target.ariaLabel);
        current_page = page_number - 1; // note minus 1 here
        pagination();
      };
      
      var prevPage = document.getElementById('prev-page');
      if(prevPage != null)prevPage.addEventListener('click', onPrevPageClick);
      
      var nextPage = document.getElementById('next-page');
      if(nextPage != null) nextPage.addEventListener('click', onNextPageClick);
      for (var i = start_page; i < end_page; i += 1) {
        var page = document.getElementById('page-'+(i + 1));
        if(page != null)page.addEventListener('click', onPageClick);
      }
    };
    
    paginationContent.innerHTML = ''; // clear
   
    var begin_index = Math.min(current_page * pageSize, resultItems.length);
    var end_index = Math.min(begin_index + pageSize, resultItems.length);
    resultContent.innerHTML = resultItems.slice(begin_index, end_index).map(function(result) {return result.item}).join('');
  
    start_page = Math.max(0, total_pages - max_page_on_show);
    end_page = start_page + Math.min(total_pages, max_page_on_show);
    var pageContent = '<div class="#search-pagination">';
    pageContent += '<div class="#search-pagination pagination">';
    pageContent += '<ul>';
    if (total_pages > 0) {
      // add prev page arrow, when no prev page not selectable
      pageContent += addPrevPage(current_page);
      for (var i = start_page; i < end_page; i += 1) {
        pageContent += addPage(i, current_page);
      }
      // add next page arrow, when no next page not selectable
      pageContent += addNextPage(current_page);
    }
    pageContent += '</ul>';
    pageContent += '</div>';
    pageContent += '</div>';
    paginationContent.innerHTML = pageContent;
    addPaginationEvents(start_page, end_page);
    resultContent.scrollTop = 0;  // scroll to top
    window.pjax && window.pjax.refresh(resultContent);
  };
  
  const inputEventFunction = function() {
    if (!isFetched) {
      console.log("Data not fetched.");
      return;
    }
    var searchText = input.value.trim().toLowerCase();
    var keywords = searchText.split(/[-\s]+/);
    
    if (keywords.length > 1) {
      keywords.push(searchText);
    }
    
    resultItems = [];
    if (searchText.length > 0) {
      // Perform local searching
      datas.forEach(function(index) {
       
        var categories = index.categories, title=index.title, content=index.content, url=index.url;
        var titleInLowerCase = title.toLowerCase();
        var contentInLowerCase = content.toLowerCase();
        var indexOfTitle = [];
        var indexOfContent = [];
        var searchTextCount = 0;
        keywords.forEach( function(keyword) {
          indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));
          indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));
        });
        
        
        // Show search results
        if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
          var hitCount = indexOfTitle.length + indexOfContent.length;
          // Sort index by position of keyword
          [indexOfTitle, indexOfContent].forEach(function(index)  {
            index.sort(function(itemLeft, itemRight)  {
              if (itemRight.position !== itemLeft.position) {
                return itemRight.position - itemLeft.position;
              }
              return itemLeft.word.length - item.word.length;
            });
          });
          var slicesOfTitle = [];
          if (indexOfTitle.length !== 0) {
            var tmp = mergeIntoSlice(0, title.length, indexOfTitle, searchText);
            searchTextCount += tmp.searchTextCountInSlice;
            slicesOfTitle.push(tmp);
          }
          
          var slicesOfContent = [];
          while (indexOfContent.length !== 0) {
            var item = indexOfContent[indexOfContent.length - 1];
            var position = item.position;
            var word = item.word;
            // Cut out 100 characters
            var start = position - 20;
            var end = position + 30;
            if (start < 0) {
              start = 0;
            }
            if (end < position + word.length) {
              end = position + word.length;
            }
            if (end > content.length) {
              end = content.length;
            }
            var tmp = mergeIntoSlice(start, end, indexOfContent, searchText);
            searchTextCount += tmp.searchTextCountInSlice;
            slicesOfContent.push(tmp);
          }
          
          
          // Sort slices in content by search text's count and hits' count
          slicesOfContent.sort( function(sliceLeft, sliceRight) {
            if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
              return sliceRight.searchTextCount - sliceLeft.searchTextCount;
            } else if (sliceLeft.hits.length !== sliceRight.hits.length) {
              return sliceRight.hits.length - sliceLeft.hits.length;
            }
            return sliceLeft.start - sliceRight.start;
          });
          // Select top N slices in content
          var upperBound = parseInt(CONFIG.localSearch.pageSize, 10);
          if (upperBound >= 0) {
            slicesOfContent = slicesOfContent.slice(0, upperBound);
          }
          var resultItem = '';
          resultItem += '<div class="#search-hits item">';
          // resultItem += '<div class="#search-hits">';
          // resultItem += '<ol class="item">'
          resultItem += '<li>'
          // resultItem += '<li>';
          var cats = categories !== undefined ? '<span>' + categories.join('<i class="ic i-angle-right"></i>') + '</span>' : '<span>No categories</span>';
          resultItem += '<a href="'+url+'">' + cats;
          if (slicesOfTitle.length !== 0) {
            // resultItem += '<li><a href="'+url}">'+highlightKeyword(title, slicesOfTitle[0])}</a>';
            resultItem += '<b>'+highlightKeyword(title, slicesOfTitle[0])+'</b><br>';
          } else {
            // resultItem += '<li><a href="'+url}">'+title}</a>';
            resultItem += '<b>'+title+'</b><br>';
          }
          
          slicesOfContent.forEach(function(slice)  {
            return resultItem += '<li class="#search-hits subitem">'+highlightKeyword(content, slice)+' ...</li>';
          });
          // resultItem += '</li>';
          resultItem += '</a>';
          resultItem += '</li>';
          // resultItem += '</ol>';
          resultItem += '</div>';
          resultItems.push({
            item: resultItem,
            id  : resultItems.length,
            hitCount:hitCount,
            searchTextCount:searchTextCount
          });
        }
      });
    }
    
    if (keywords.length === 1 && keywords[0] === '') {
      resultContent.innerHTML = '<div id="no-result"><i></i></div>';
    } else if (resultItems.length === 0) {
      resultContent.innerHTML = '<div id="no-result"><i></i></div>';
    } else {
      resultItems.sort(function(resultLeft, resultRight)  {
        if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
          return resultRight.searchTextCount - resultLeft.searchTextCount;
        } else if (resultLeft.hitCount !== resultRight.hitCount) {
          return resultRight.hitCount - resultLeft.hitCount;
        }
        return resultRight.id - resultLeft.id;
      });
    }
    
    // Do pagination
    total_pages = Math.ceil(resultItems.length / pageSize);
    pagination();
  }
  
  const fetchData = function() {
    fetch(CONFIG.root + searchPath)
      .then(function(response) {return response.text()} )
      .then( function(res) {
        // Get the contents from search data
        isFetched = true;
        datas = isXml ? [new DOMParser().parseFromString(res, 'text/xml').querySelectorAll('entry')].map( function(element) {
          return {
            title  : element.querySelector('title').textContent,
            content: element.querySelector('content').textContent,
            url    : element.querySelector('url').textContent
          };
        }) : JSON.parse(res);
        // Only match articles with not empty titles
        datas = datas.filter(function(data) {return data.title} ).map( function(data) {
          data.title = data.title.trim();
          data.content = data.content ? data.content.trim().replace(/<[^>]+>/g, '') : '';
          data.url = decodeURIComponent(data.url).replace(/\/{2,}/g, '/');
          return data;
        });
        // Remove loading animation
        document.getElementById('search-hits').innerHTML = '<i></i>';
        inputEventFunction();
      });
  };
  if (CONFIG.localSearch.preload) {
    console.log("fetch data.");
    fetchData();
  }
  if (CONFIG.localSearch.trigger === 'auto') {
    input.addEventListener('input', inputEventFunction);
  } else {
    document.querySelector('.search-icon').addEventListener('click', inputEventFunction);
    input.addEventListener('keypress',function(event) {
      if (event.key === 'Enter') {
        inputEventFunction();
      }
    });
  }
  // Handle and trigger popup window
  document.querySelectorAll('.popup-trigger').forEach( function(element) {
    element.addEventListener('click', function()  {
      document.body.style.overflow = 'hidden';
      document.querySelector('.search-pop-overlay').classList.add('search-active');
      input.focus();
      if (!isFetched) fetchData();
    });
  });
  // Handle and trigger popup window
  $.each('.search', function(element) {
    element.addEventListener('click', function() {
      document.body.style.overflow = 'hidden';
      transition(siteSearch, 'shrinkIn', function() {
          $('.search-input').focus();
        }) // transition.shrinkIn
    });
  });
  // Monitor main search box
  const onPopupClose = function() {
    document.body.style.overflow = '';
    transition(siteSearch, 0); // "transition.shrinkOut"
  };
  siteSearch.addEventListener('click', function(event) {
    if (event.target === siteSearch) {
      onPopupClose();
    }
  });
  $('.close-btn').addEventListener('click', onPopupClose);
  window.addEventListener('pjax:success', onPopupClose);
  window.addEventListener('keyup', function(event) {
    if (event.key === 'Escape') {
      onPopupClose();
    }
  });
};
  1. 修改 script.js

shoka/scripts/generaters/script.js 中主要是读取配置,添加如下代码

...........  // 省略若干代码
if(config.algolia) {
  siteConfig.search = {
    appID    : config.algolia.appId,
    apiKey   : config.algolia.apiKey,
    indexName: config.algolia.indexName,
    hits     : theme.search.hits
  }
}
// 以下为需要添加的代码
if(config.search) {
  siteConfig.localSearch = {
    enable: config.search.enable,
    path: config.search.path,
    field: config.search.field,
    format: config.search.format,
    limit: config.search.limit,
    content: config.search.content,
    unescape: config.search.unescape,
    preload:  config.search.preload,
    trigger: config.search.trigger,
    pageSize: config.search.pageSize
  }
}
  1. 修改 pjax.js

shoka/source/js/_app/pjax.js 中是启动搜索功能的部分,这里在两个配置都有的情况下默认使用本地搜索而不是 Algolia

if (CONFIG.localSearch != null) {
  localSearch(pjax)
}else if(CONFIG.search != null) {
  algoliaSearch(pjax)
}
  1. 添加配置

最后在 hexo 配置(最外层的 _config.yml )中添加 search 配置

search:
  enable: true
  path: search.json # search.xml
  field: post
  format: html
  limit: 10000
  content: true
  unescape: true  
  preload:  true  
  trigger: "auto" 
  pageSize: 10

# 其他配置

# 修改图库

  • 默认的图片列表位于 /themes/shoka/_images.yml
  • 在图片列表 yml 文件中,写上任意外链图片地址
  • 此处使用 PICUI 存储图片
- https://img.picui.cn/free/2024/08/21/66c4d0d10b26a.png
- https://img.picui.cn/free/2024/08/21/66c4d0a9ead74.png
- https://img.picui.cn/free/2024/08/21/66c4d0a5b6deb.jpg
- https://img.picui.cn/free/2024/08/21/66c4d0a4c555f.jpg
- https://img.picui.cn/free/2024/08/21/66c4d0a4a4f14.jpg
- https://img.picui.cn/free/2024/08/21/66c4d0a3d8384.jpg
- https://img.picui.cn/free/2024/08/21/66c4d0a2ef2fd.jpg
- https://img.picui.cn/free/2024/08/21/66c4d07600f66.jpg
- https://img.picui.cn/free/2024/08/21/66c4d07601b05.jpg
- https://img.picui.cn/free/2024/08/21/66c5702de9e58.jpg
- https://img.picui.cn/free/2024/08/21/66c5702be1c36.jpg

# algolia 搜索功能

  • 使用 Github 账号登录 Algolia
  • 进入 Dashboard - Search - Index 页面,选择上方 + Create Index 创建索引,索引名称建议为 shokaX
  • 进入 Dashboard - Settings - API Keys 页面,复制如下数据到上方配置中。
页面数据对应配置
Application IDappId
Search-Only API KeyapiKey
Admin API KeyadminApiKey
创建的索引名indexName
  • 在博客部署前运行 hexo algolia 上传索引,可在 Dashboard - Search - Index 页面中查看。
  • 执行 hexo d 更新博客,将所有文件更新到 github 即可实现搜索功能

# 问题汇总

# GitHub 连接不上

  • 解决方法:取消代理
git config --global --unset http.proxy
git config --global --unset https.proxy
  • 查看代理
git config --global --get http.proxy
git config --global --get https.proxy

# <br> 换行符识别不了

  • 解决方法:
# _config.yml
markdown:
  render: # 渲染器设置
    html: true # 过滤 HTML 标签

# 代码块渲染问题

  • 解决方法:修改 package.json 文件
  1. 修改 hexo 的 package.json 的文件内容,将 shoka 的 example 目录中的 package.json 文件的内容拷贝到 hexo 的 package.json 中即可

  2. 修改完 package.json 文件内容后执行下面的命令降级 hexo

npm install -g hexo@5.4.2
  1. 然后在 hexo 目录下更新依赖
npm install
  1. 然后重新生成即可
hexo clean && hexo g && hexo s

# 参考文章

  • Hexo - 零基础搭建个人博客
  • ShokaX 主题安装
  • Hexo 上 markdown 图片路径与 Typora 保持一致
  • 在 Hexo 中引入本地图片
  • 代码块渲染问题
  • ShokaX 主题使用文档
  • 本地搜索功能实现