hugo 使用 @

时隔四年,重新搭起个人博客。

网站从当初的 hexo 换为了 hugo 搭建,使用 hugo-coder 作为主题,进行了部分魔改。

hugo 安装 @

由于博主使用的 manjaro 系统作为日常开发、办公系统,可以使用 yay 直接进行安装

yay -S hugo

如果使用其他操作系统,可以参考 hugo 官方安装教程 进行安装

hugo 基础命令 @

首先学习下 hugo 的部分命令

hugo version         # 查看版本
hugo new site myblog # 创建目录结构
# 更改主题
cd myblog
git init
git submodule add https://github.com/luizdepra/hugo-coder.git themes/hugo-coder
echo "theme = 'hugo-coder'" >> hugo.toml

创建新文章

hugo new content content/posts/my-first-post.md

启动服务

# 启动服务
hugo serve --disableFastRender

可以通过 127.0.0.1:1313 端口访问本地博客,对于文档的更新,支持实时渲染

twikoo 评论系统 @

接下来介绍一下魔改部分, 首先 hugo-coder 自身没有支持 twikoo 评论系统,依照 twikoo 官方文档

首先需要搭建 twikoo 后端服务

  • 搭建 mongodb 云数据库
  • 搭建 twikoo 后端服务,并且连接云数据库

为此,我使用了官方文档推荐的 mongodb 云服务 和 netlify 来搭建

可以参考官方的 netlify 部署方式

搭建完成后,便可以在主题路径 themes/hugo-coder/layouts/partials/posts 下新建文件 twikoo.html,内容如下

<script src="https://cdn.jsdelivr.net/npm/twikoo@1.6.41/dist/twikoo.all.min.js"></script>
<div id="tcomment"></div>
<style>
    .twikoo {
        background-color: var(--card-background);
        border-radius: var(--card-border-radius);
        box-shadow: var(--shadow-l1);
        padding: var(--card-padding);
    }

    :root[data-scheme="dark"] {
        --twikoo-body-text-color-main: rgba(255, 255, 255, 0.9);
        --twikoo-body-text-color: rgba(255, 255, 255, 0.7);
    }

    .twikoo .el-input-group__prepend,
    .twikoo .tk-action-icon,
    .twikoo .tk-submit-action-icon,
    .twikoo .tk-time,
    .twikoo .tk-comments-no,
    .twikoo .tk-comments-count {
        color: var(--twikoo-body-text-color);
    }

    .twikoo .el-input__inner,
    .twikoo .el-textarea__inner,
    .twikoo .tk-preview-container,
    .twikoo .tk-content,
    .twikoo .tk-nick,
    .twikoo .tk-send {
        color: var(--twikoo-body-text-color-main);
    }

    .twikoo .el-button {
        color: var(--twikoo-body-text-color) !important;
    }

    .twikoo .el-input__count {
        color: var(--twikoo-body-text-color) !important;
    }

    .OwO .OwO-body {
        background-color: var(--body-background) !important;
        color: var(--body-text-color) !important;
    }
</style>

{{- with .Site.Params.twikoo -}}
<script>
    twikoo.init({
        envId: 'https://xxxxxx.app/.netlify/functions/twikoo',
        el: '#tcomment', // 容器元素
    })
</script>
{{- end -}}

修改 themes/hugo-coder/layouts/posts/single.html 文件, 在底部<footer>标签内,增加一行

<footer>
        {{ partial "posts/series.html" . }}
        {{ partial "posts/twikoo.html" . }}
        ....
      </footer>

到此,文章底部已经可以使用 twikoo 系统进行评论了

添加数学公式支持 @

由于我的一部分技术文章会写一些数学公式,准备使用 mathjax 支持对数学公式的渲染。

因此需要手动添加这部分功能

在路径 themes/hugo-coder/layouts/partials/head 下新建文件 mathjax.html, 内容如下

<script type="text/javascript"
        async
        src="https://cdn.bootcss.com/mathjax/2.7.3/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
MathJax.Hub.Config({
  tex2jax: {
    inlineMath: [['$','$'], ['\\(','\\)']],
    displayMath: [['$$','$$'], ['\[\[','\]\]']],
    processEscapes: true,
    processEnvironments: true,
    skipTags: ['script', 'noscript', 'style', 'textarea', 'pre'],
    TeX: { equationNumbers: { autoNumber: "AMS" },
         extensions: ["AMSmath.js", "AMSsymbols.js"] }
  }
});

MathJax.Hub.Queue(function() {
    // Fix <code> tags after MathJax finishes running. This is a
    // hack to overcome a shortcoming of Markdown. Discussion at
    // https://github.com/mojombo/jekyll/issues/199
    var all = MathJax.Hub.getAllJax(), i;
    for(i = 0; i < all.length; i += 1) {
        all[i].SourceElement().parentNode.className += ' has-jax';
    }
});
</script>

<style>
code.has-jax {
    font: inherit;
    font-size: 100%;
    background: inherit;
    border: inherit;
    color: #515151;
}
</style>

<script src="//yihui.org/js/math-code.js" defer></script>
<script defer
  src="//mathjax.rstudio.com/latest/MathJax.js?config=TeX-MML-AM_CHTML">
</script>

在文件 themes/hugo-coder/layouts/partials/head.html 的底部添加

{{ partial "head/mathjax.html" . }}

自此,数学公式可以正确渲染,但这中间遇到了一个问题,如果公式包含三个以上大括号,会出现渲染问题。

经过互联网查询,参考了 这位大佬的 方案 , 添加了如下代码

<script src="//yihui.org/js/math-code.js" defer></script>
<script defer
  src="//mathjax.rstudio.com/latest/MathJax.js?config=TeX-MML-AM_CHTML">
</script>

但是依照他的方案,需要在公式前后用 `` 标注

在文章头部添加字数统计 @

hugo 框架提供了一些函数调用,使得可以轻松获取文章的统计字数。

在文件 themes/hugo-coder/layouts/posts/single.html 的标签<div class="post-meta">底部添加一个 span 来实现

<div class="post-meta">
  <div class="date">
    <span class="posted-on">
      <i class="fa-solid fa-calendar" aria-hidden="true"></i>
      <time datetime="{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}">
        {{ .Date | time.Format (.Site.Params.dateFormat | default "January 2, 2006" ) }}
      </time>
    </span>
    <span class="reading-time">
      <i class="fa-solid fa-clock" aria-hidden="true"></i>
      {{ i18n "reading_time" .ReadingTime }}
    </span>
    <span class="post-word-count">
      <i class="fa fa-pie-chart" aria-hidden="true" style="margin-left: 10px;"></i>
      {{ .WordCount }} 字数
    </span>
</div>

添加目录侧边栏 @

由于 hugo 主题没有在文章内容页面增加目录侧边栏,我本身对前端技术不是特别懂,为了方便,因此利用 AI 给自己写了一个效果还不错的侧边栏,效果如下

新建文件 themes/hugo-coder/layouts/partials/toc.html

<!-- 侧边栏目录 -->
   
  <aside class="table-of-contents">
    <div class="toc-container">
      {{ .TableOfContents }}
    </div>
  </aside>

新建文件 themes/hugo-coder/assets/css/top.css

/* CSS变量定义 */
:root {
    --bg-color: 255, 255, 255;
    --text-color: #333;
    --heading-color: #2c3e50;
    --border-color: 238, 238, 238;
    --accent-color: #42b983;
    --accent-rgb: 66, 185, 131;
    --scrollbar-color: 221, 221, 221;
    --scrollbar-track: 245, 245, 245;
}

/* 深色主题 */
@media (prefers-color-scheme: dark) {
    :root {
        --bg-color: 30, 30, 30;
        --text-color: #e0e0e0;
        --heading-color: #ffffff;
        --border-color: 60, 60, 60;
        --accent-color: #42d392;
        --accent-rgb: 66, 211, 146;
        --scrollbar-color: 80, 80, 80;
        --scrollbar-track: 50, 50, 50;
    }
}

/* 主容器样式 */
.table-of-contents {
    position: fixed;
    left: -380px;
    top: 30px;
    width: 400px;
    height: calc(100vh - 60px);
    background: rgba(var(--bg-color), 0.8);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
    z-index: 1000;
    border-right: 1px solid rgba(var(--border-color), 0.3);
    color: var(--text-color);
    border-radius: 12px;
    transition: none;
}

/* 动画类 */
.table-of-contents.hidden {
    left: -380px;
    transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}

.table-of-contents.visible {
    left: 0;
    transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
}

/* 触发区域样式 */
.table-of-contents::after {
    content: "☰";
    position: absolute;
    right: -40px;
    top: calc(50% - 30px);
    width: 40px;
    height: 60px;
    line-height: 60px;
    text-align: center;
    background: rgba(var(--bg-color), 0.8);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border-radius: 12px;
    box-shadow: 4px 0 15px rgba(0, 0, 0, 0.08);
    cursor: pointer;
    font-size: 1.8rem;
    color: var(--text-color);
    transition: color 0.3s ease;
    border: 1px solid rgba(var(--border-color), 0.3);
}

/* 内容容器样式 */
.toc-container {
    padding: 2rem;
    height: 100%;
    overflow-y: auto;
}

/* 标题样式 */
.toc-container h2 {
    font-size: 2.2rem;
    margin-bottom: 2.5rem;
    font-weight: 600;
    color: #2c3e50;
    border-bottom: 2px solid rgba(var(--border-color), 0.5);
    padding-bottom: 0.8rem;
}

/* 目录主体样式 */
#TableOfContents {
    font-size: 1.6rem;
}

#TableOfContents ul {
    list-style: none;
    padding-left: 2rem;
}

#TableOfContents ul li {
    margin: 1.2rem 0;
    line-height: 2;
}

/* 链接样式 */
#TableOfContents a {
    color: var(--text-color);
    text-decoration: none;
    padding: 0.6rem 1.2rem;
    border-left: 3px solid transparent;
    display: block;
    transition: all 0.2s ease;
}

#TableOfContents a:hover {
    color: var(--accent-color);
    background: rgba(var(--accent-rgb), 0.15);
    border-left-color: var(--accent-color);
    transform: translateX(4px);
}

#TableOfContents a.active {
    color: var(--accent-color);
    background: rgba(var(--accent-rgb), 0.15);
    border-left-color: var(--accent-color);
    font-weight: 500;
}

/* 多级目录样式 */
#TableOfContents ul ul {
    font-size: 1.4rem;
    opacity: 0.9;
}

/* 滚动条样式 */
.table-of-contents::-webkit-scrollbar,
.toc-container::-webkit-scrollbar {
    width: 5px;
}

.table-of-contents::-webkit-scrollbar-thumb,
.toc-container::-webkit-scrollbar-thumb {
    background: rgba(var(--scrollbar-color), 0.8);
    border-radius: 4px;
}

.table-of-contents::-webkit-scrollbar-track,
.toc-container::-webkit-scrollbar-track {
    background: rgba(var(--scrollbar-track), 0.5);
}

/* 响应式设计 */
@media (max-width: 1200px) {
    .table-of-contents {
        display: none;
    }

    .post-content {
        margin-left: 0;
    }
}

hugo.toml 配置注入 css,并更改标题显示配置

[params]
  ....
  customCSS = ["css/top.css"]
  
 
[markup]
[markup.highlight]
  noClasses = false
[markup.tableOfContents]
  startLevel = 1   # 从一级标题开始显示
  endLevel = 6     # 到六级标题结束
  ordered = false  # 使用无序列表

在 themes/hugo-coder/layouts/posts/single.html 下的<section class="container post">里注入 js 代码,实现侧边栏的动态隐藏滑动效果

<section class="container post">

  <script>
    document.addEventListener('DOMContentLoaded', function () {
      const toc = document.querySelector('.table-of-contents');
      if (!toc) return;

      // 初始状态设置为隐藏
      toc.classList.add('hidden');
      let timer;

      // 鼠标交互处理
      toc.addEventListener('mouseenter', () => {
        clearTimeout(timer);
        toc.classList.add('visible');
        toc.classList.remove('hidden');
      });

      toc.addEventListener('mouseleave', () => {
        timer = setTimeout(() => {
          toc.classList.remove('visible');
          toc.classList.add('hidden');
        }, 300);
      });

      // 滚动监听逻辑
      const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
      const tocLinks = document.querySelectorAll('#TableOfContents a');

      const observerOptions = {
        rootMargin: '-80px 0px -80px 0px',
        threshold: [0, 0.25, 0.5, 0.75, 1]
      };

      let currentActiveLink = null;
      let isScrolling = false;

      const observer = new IntersectionObserver(entries => {
        if (isScrolling) return;

        entries.forEach(entry => {
          const targetId = '#' + entry.target.id;
          const link = Array.from(tocLinks).find(link => link.getAttribute('href') === targetId);

          if (!link) return;

          if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
            if (currentActiveLink) {
              currentActiveLink.classList.remove('active');
            }
            link.classList.add('active');
            currentActiveLink = link;
          }
        });
      }, observerOptions);

      // 点击处理
      tocLinks.forEach(link => {
        link.addEventListener('click', (e) => {
          e.preventDefault();
          const targetId = link.getAttribute('href');
          const targetElement = document.querySelector(targetId);

          if (targetElement) {
            isScrolling = true;

            if (currentActiveLink) {
              currentActiveLink.classList.remove('active');
            }

            link.classList.add('active');
            currentActiveLink = link;

            window.scrollTo({
              top: targetElement.offsetTop - 80,
              behavior: 'smooth'
            });

            setTimeout(() => {
              isScrolling = false;
            }, 1000);
          }
        });
      });

      // 为每个标题添加观察
      headings.forEach(heading => {
        if (!heading.id) {
          heading.id = heading.textContent.trim().toLowerCase().replace(/\s+/g, '-');
        }
        observer.observe(heading);
      });
    });
  </script>
  .....

到此目录侧边栏就实现完成了

使用 github actions 部署个人网站 @

参考 hugo 官方提供的方法,使用 github action 进行部署,可以考虑买个域名,转发到 github 博客子域名上

https://gohugo.io/hosting-and-deployment/hosting-on-github/

在 DNS 域名服务商,添加 CNAME 转发到自己的 github 子域名即可。