如何构建我的hugo博客-这是一个系列

前言

在上一篇文章“ Hugo博客搭建_基础 😊 ”中,我们讲解了如何搭建一个Hugo博客,这一篇文章将会讲解如何配置Hugo博客。

基础配置文件

我们曾经在hugo.yaml中配置本博客的一些功能,我们将会在这一部分补充和讨论剩余的配置:

hugo.yaml 101 lines
# Basic Configuration (基础配置)
baseURL: ""               # Site URL (网站 URL)(e.g. https://downmars.github.io/)
title: ""                # Site title (网站标题)(e.g. DLog)
theme: ""                # Theme name (主题名称)(e.g. PaperMod, in /themes/)

# Feature Toggles (功能开关)
enableEmoji: true         # Enable emoji support (启用表情支持)(e.g. https://gohugo.io/quick-reference/emojis/#smileys--emotion)
enableRobotsTXT: true     # Enable search engine support (启用搜索引擎支持)
hasCJKLanguage: true      # Enable CJK language support (启用中日韩语言支持)
buildDrafts: false        # Build draft posts (是否构建草稿文章)
buildFuture: false        # Build future posts (是否构建未来日期文章) 
buildExpired: false       # Build expired posts (是否构建过期文章)

# Parameters Configuration (参数配置)
params:
  # Theme Settings (主题设置)
  defaultTheme: dark      # Default theme mode (默认主题模式)
  disableThemeToggle: false # Allow theme switching (允许主题切换)
 
  # Display Features (显示功能)
  ShowShareButtons: true    # Show social share buttons (显示分享按钮)
  ShowCodeCopyButtons: true # Show code copy buttons (显示代码复制按钮)
  ShowReadingTime: true    # Show reading time estimate (显示阅读时间)
  ShowWordCount: true      # Show word count (显示字数统计)
  ShowPostNavLinks: true   # Show post navigation (显示文章导航)
  ShowBreadCrumbs: true    # Show breadcrumb navigation (显示面包屑导航)
  ShowToc: true           # Show table of contents (显示目录)
  TocOpen: true          # TOC expanded by default (目录默认展开)
  
  math: true

  fancybox: true # Display Images (显示图片)

 # Comment System (评论系统)
  comments: true          # Enable comments (启用评论)
  giscus:                # Giscus configuration (Giscus 配置)
    repo: ""
    repoId: ""
    category: ""
    categoryId: ""
    mapping: "pathname"
    strict: "0"
    reactionsEnabled: "1"
    emitMetadata: "0"
    inputPosition: "bottom"
    lightTheme: "light"
    darkTheme: "dark"
    lang: "zh-CN"
    crossorigin: "anonymous"

  fuseOpts: # refer: https://sonnycalcr.github.io/posts/build-a-blog-using-hugo-papermod-github-pages/#%e9%85%8d%e7%bd%ae%e6%90%9c%e7%b4%a2
    isCaseSensitive: false # 是否大小写敏感
    shouldSort: true # 是否排序
    location: 0
    distance: 1000
    threshold: 0.4
    minMatchCharLength: 0
    # limit: 10 # refer: https://www.fusejs.io/api/methods.html#search
    keys: ["title", "permalink", "summary", "content"]
    includeMatches: true

  # Assets Files (资源文件)
  assets:
    favicon: ""          # Site favicon (网站图标)
    favicon16x16: ""     # Small favicon (小图标)
    favicon32x32: ""     # Medium favicon (中图标)
    apple_touch_icon: "" # iOS icon (iOS 图标)
    safari_pinned_tab: "" # Safari icon (Safari 图标)

# Multilingual Support (多语言支持)
languages:
  zh:
    languageCode: "zh-CN"
    languageName: "简体中文"
    contentDir: "content/zh"
    weight: 1
    menu:
      main:
        - identifier: posts
          name: "Posts"
          url: "/posts/"
          weight: 1

# Output Settings (输出设置)
outputs:
  home:
    - HTML
    - RSS
    - JSON               # Required for search (搜索功能需要)

# Rendering Configuration (渲染配置)
markup:
  goldmark:
    renderer:
      unsafe: true      # Allow HTML in markdown (允许 Markdown 中的 HTML)
  highlight:
    codeFences: true    # Enable code highlighting (启用代码高亮)
    guessSyntax: true   # Guess code language (猜测代码语言)
    lineNos: true      # Show line numbers (显示行号)
    style: dracula     # Code highlight theme (代码高亮主题)
    lineNumbersInTable: true 

功能开关

表情支持

通过启用此功能,我们能够从 Github Emoji API 和 Unicode 完整表情符号列表中读取数据。博主显出极高兴的样子,将两个指头的敲着键盘,点头说,“对呀对呀!……表情有两样写法,你知道么?”

Shortcode: :drooling_face:🤤

Unicode: &#x1F924🤤

关于对应的表情和文档可以查询:

参数配置

图片放大

参考于: Hugo PaperMod 主题精装修

A beautiful landscape
Sonny_boy

This is a caption for the image

我们使用引入 fancybox 来实现:

layouts/shortcodes/figure.html中加入:

layouts/shortcodes/figure.html 38 lines
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js"></script>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.css" />
<script src="https://cdn.jsdelivr.net/gh/fancyapps/fancybox@3.5.7/dist/jquery.fancybox.min.js"></script>

<a data-fancybox="gallery" href="{{ .Get "src" }}">
<figure{{ if or (.Get "class") (eq (.Get "align") "center") }} class="
           {{- if eq (.Get "align") "center" }}align-center {{ end }}
           {{- with .Get "class" }}{{ . }}{{- end }}"
{{- end -}}>
    {{- if .Get "link" -}}
        <a href="{{ .Get "link" }}"{{ with .Get "target" }} target="{{ . }}"{{ end }}{{ with .Get "rel" }} rel="{{ . }}"{{ end }}>
    {{- end }}
    <img loading="lazy" src="{{ .Get "src" }}{{- if eq (.Get "align") "center" }}#center{{- end }}"
         {{- if or (.Get "alt") (.Get "caption") }}
         alt="{{ with .Get "alt" }}{{ . }}{{ else }}{{ .Get "caption" | markdownify| plainify }}{{ end }}"
         {{- end -}}
         {{- with .Get "width" }} width="{{ . }}"{{ end -}}
         {{- with .Get "height" }} height="{{ . }}"{{ end -}}
    /> <!-- Closing img tag -->
    {{- if .Get "link" }}</a>{{ end -}}
    {{- if or (or (.Get "title") (.Get "caption")) (.Get "attr") -}}
        <figcaption>
            {{ with (.Get "title") -}}
                {{ . }}
            {{- end -}}
            {{- if or (.Get "caption") (.Get "attr") -}}<p>
                {{- .Get "caption" | markdownify -}}
                {{- with .Get "attrlink" }}
                    <a href="{{ . }}">
                {{- end -}}
                {{- .Get "attr" | markdownify -}}
                {{- if .Get "attrlink" }}</a>{{ end }}</p>
            {{- end }}
        </figcaption>
    {{- end }}
</figure>
</a>

数学公示

参考于: Hugo博客添加LaTeX语法支持 Hugo PaperMod 主题精装修 MathJax 与 Markdown 的究极融合

上述博客中提到了相同的问题即 ”对于mathjax与markdown格式中,hugo在渲染的过程中将_渲染为了<em>标签,导致mathjax在渲染的时候找不到原来正确的公示“。

首先,我们在hugo.yaml中添加:

hugo.yaml 2 lines
params:
  math: true

接着,我们在layouts/partials/mathjax.html中添加:

layouts/partials/mathjax.html 25 lines
<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>

接着,在layouts/partials/extend_footer.html中添加:

layouts/partials/extend_footer.html 25 lines
<script>
  (function () {
    var i, text, code, codes = document.getElementsByTagName("code");
    for (i = 0; i < codes.length; ) {
      code = codes[i];
      if (code.parentNode.tagName !== "PRE" && code.childElementCount === 0) {
        text = code.textContent;
        if (/^\$[^$]/.test(text) && /[^$]\$$/.test(text)) {
          text = text.replace(/^\$/, "\\(").replace(/\$$/, "\\)");
          code.textContent = text;
        }
        if (
          /^\\\((.|\s)+\\\)$/.test(text) ||
          /^\\\[(.|\s)+\\\]$/.test(text) ||
          /^\$(.|\s)+\$$/.test(text) ||
          /^\\begin\{([^}]+)\}(.|\s)+\\end\{[^}]+\}$/.test(text)
        ) {
          code.outerHTML = code.innerHTML; // remove <code></code>
          continue;
        }
      }
      i++;
    }
  })();
</script>

最后,在layouts/partials/extend_head.html中添加来判断hugo.yaml中是否启用来决定是否渲染:

layouts/partials/extend_head.html 3 lines
{{ if or .Params.math .Site.Params.math }}
{{- partial "mathjax.html" .}}
{{ end }}

本处引用的方法通过将带有公式部分使用代码block装饰起来,避免内容被修改,再将代码block去除,完整将其送给渲染工具。

没做处理之前,以下代码无法渲染:

$$ \frac{\partial E(\boldsymbol{w})}{\partial z_j} = \sum\limits_{k}\frac{\partial E(\boldsymbol{w})}{\partial y_{k}}\frac{\partial y_k}{\partial z_{j}}= \sum\limits_{k} (y_{k}- \hat{y}_{k}) w_{kj}^{(2)} \tag{5.11} $$

现有几个问题待解决:1、白色黑色主题字体颜色未翻转;2、字体大小没有自动渲染正确,在中间的一个步骤,使能够正常匹配主题的颜色和字体大小,但是完全渲染过后就会出现刚才提及的问题,可能的解决方法参考于 解决 mathjax 数学公式渲染的字体大小问题

目录配置

对于自带的目录,只显示在文章最上面,既不能让读者随时掌握到阅读进度,也不够优雅,所以我在参考了 在PaperMod中引入侧边目录和阅读进度显示 基础上做了一些改动

  • 自动编号
  • 当前浏览章节下划线显示 创建 layout/partials/toc.html ,

layouts/partials/toc.hmtl 265 lines
<!-- 目录侧边栏 -->
{{- $headers := findRE "<h[1-6].*?>(.|\n])+?</h[1-6]>" .Content -}}
{{- $has_headers := ge (len $headers) 1 -}}
{{- if $has_headers -}}
<aside id="toc-container" class="toc-container wide">
    <div class="toc">
        <details {{if (.Param "TocOpen") }} open{{ end }}>
            <summary accesskey="c" title="(Alt + C)">
                <span class="details">{{- i18n "toc" | default "Table of Contents" }}</span>
            </summary>

            <div class="inner">
                {{- $largest := 6 -}}
                {{- range $headers -}}
                {{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}}
                {{- $headerLevel := len (seq $headerLevel) -}}
                {{- if lt $headerLevel $largest -}}
                {{- $largest = $headerLevel -}}
                {{- end -}}
                {{- end -}}

                {{- $firstHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers 0) 1) 0)) -}}

                {{- $.Scratch.Set "bareul" slice -}}
                <ul>
                    {{- range seq (sub $firstHeaderLevel $largest) -}}
                    <ul>
                        {{- $.Scratch.Add "bareul" (sub (add $largest .) 1) -}}
                        {{- end -}}
                        {{- range $i, $header := $headers -}}
                        {{- $headerLevel := index (findRE "[1-6]" . 1) 0 -}}
                        {{- $headerLevel := len (seq $headerLevel) -}}

                        {{/* get id="xyz" */}}
                        {{- $id := index (findRE "(id=\"(.*?)\")" $header 9) 0 }}

                        {{- /* strip id="" to leave xyz, no way to get regex capturing groups in hugo */ -}}
                        {{- $cleanedID := replace (replace $id "id=\"" "") "\"" "" }}
                        {{- $header := replaceRE "<h[1-6].*?>((.|\n])+?)</h[1-6]>" "$1" $header -}}

                        {{- if ne $i 0 -}}
                        {{- $prevHeaderLevel := index (findRE "[1-6]" (index $headers (sub $i 1)) 1) 0 -}}
                        {{- $prevHeaderLevel := len (seq $prevHeaderLevel) -}}
                        {{- if gt $headerLevel $prevHeaderLevel -}}
                        {{- range seq $prevHeaderLevel (sub $headerLevel 1) -}}
                        <ul>
                            {{/* the first should not be recorded */}}
                            {{- if ne $prevHeaderLevel . -}}
                            {{- $.Scratch.Add "bareul" . -}}
                            {{- end -}}
                            {{- end -}}
                            {{- else -}}
                            </li>
                            {{- if lt $headerLevel $prevHeaderLevel -}}
                            {{- range seq (sub $prevHeaderLevel 1) -1 $headerLevel -}}
                            {{- if in ($.Scratch.Get "bareul") . -}}
                        </ul>
                        {{/* manually do pop item */}}
                        {{- $tmp := $.Scratch.Get "bareul" -}}
                        {{- $.Scratch.Delete "bareul" -}}
                        {{- $.Scratch.Set "bareul" slice}}
                        {{- range seq (sub (len $tmp) 1) -}}
                        {{- $.Scratch.Add "bareul" (index $tmp (sub . 1)) -}}
                        {{- end -}}
                        {{- else -}}
                    </ul>
                    </li>
                    {{- end -}}
                    {{- end -}}
                    {{- end -}}
                    {{- end }}
                    <li>
                        <a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a>
                        {{- else }}
                    <li>
                        <a href="#{{- $cleanedID -}}" aria-label="{{- $header | plainify -}}">{{- $header | safeHTML -}}</a>
                        {{- end -}}
                        {{- end -}}
                        {{- $firstHeaderLevel := $largest }}
                        {{- $lastHeaderLevel := len (seq (index (findRE "[1-6]" (index $headers (sub (len $headers) 1)) 1) 0)) }}
                    </li>
                    {{- range seq (sub $lastHeaderLevel $firstHeaderLevel) -}}
                    {{- if in ($.Scratch.Get "bareul") (add . $firstHeaderLevel) }}
                </ul>
                {{- else }}
                </ul>
                </li>
                {{- end -}}
                {{- end }}
                </ul>
            </div>
        </details>
    </div>
</aside>
<script>
    let activeElement;
    let elements;
    let headerCounters = {};

    // 重置所有计数器
    function resetHeaderCounters() {
        headerCounters = {};
    }

    // header计数器逻辑
    function getHeaderNumber(element, headerLevel) {
        // 获取之前的所有标题元素
        let prevElements = Array.from(document.querySelectorAll('h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]'));
        let currentIndex = prevElements.indexOf(element);
        let counters = new Array(6).fill(0); // 初始化6级计数器
        let numbers = [];
        
        for (let i = 0; i <= currentIndex; i++) {
            let currentElement = prevElements[i];
            let currentLevel = parseInt(currentElement.tagName.substring(1)) - 1; // 转为0-based
            
            // 重置子级计数器
            for (let l = currentLevel + 1; l < 6; l++) {
                counters[l] = 0;
            }
            
            // 递增当前级计数器
            counters[currentLevel]++;
            
            // 如果是目标元素则记录编号
            if (currentElement === element) {
                for (let l = 0; l <= currentLevel; l++) {
                    if (counters[l] > 0) {
                        numbers.push(counters[l]);
                    }
                }
                break;
            }
        }
        
        return numbers.join('.');
    }

    // 更新目录项的显示
    function updateTocDisplay(element, tocLink) {
        const headerLevel = parseInt(element.tagName.substring(1));
        const currentNumber = getHeaderNumber(element, headerLevel);
        
        // 移除之前可能存在的编号
        let linkText = tocLink.textContent;
        linkText = linkText.replace(/^\d+(\.\d+)*\s+/, '');
        
        // 添加新的编号
        tocLink.textContent = `${currentNumber} ${linkText}`;
    }

    // 获取元素的顶部偏移
    function getOffsetTop(element) {
        if (!element.getClientRects().length) {
            return 0;
        }
        let rect = element.getBoundingClientRect();
        let win = element.ownerDocument.defaultView;
        return rect.top + win.pageYOffset;   
    }

    // 检查TOC位置
    function checkTocPosition() {
        const width = document.body.scrollWidth;
        const main = parseInt(getComputedStyle(document.body).getPropertyValue('--article-width'), 10);
        const toc = parseInt(getComputedStyle(document.body).getPropertyValue('--toc-width'), 10);
        const gap = parseInt(getComputedStyle(document.body).getPropertyValue('--gap'), 10);
    
        if (width - main - (toc * 2) - (gap * 4) > 0) {
            document.getElementById("toc-container").classList.add("wide");
        } else {
            document.getElementById("toc-container").classList.remove("wide");
        }
    }

    // 初始化时的处理
    document.addEventListener('DOMContentLoaded', function (event) {
        checkTocPosition();
    
        elements = document.querySelectorAll('h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]');
        if (elements.length > 0) {
            // 设置第一个标题为活动状态
            activeElement = elements[0];
            const id = encodeURI(activeElement.getAttribute('id')).toLowerCase();
            document.querySelector(`.inner ul li a[href="#${id}"]`).classList.add('active');
        }
    
        // 初始化所有标题的编号
        if (elements && elements.length > 0) {
            elements.forEach(element => {
                const id = encodeURI(element.getAttribute('id')).toLowerCase();
                const tocLink = document.querySelector(`.inner ul li a[href="#${id}"]`);
                
                if (tocLink) {
                    updateTocDisplay(element, tocLink);
                }
            });
        }

        // 添加返回顶部链接的事件监听
        const topLink = document.getElementById('top-link');
        if (topLink) {
            topLink.addEventListener('click', (event) => {
                event.preventDefault();
                window.scrollTo({ top: 0, behavior: 'smooth' });
            });
        }
    }, false);

    // 窗口大小改变时的处理
    window.addEventListener('resize', function(event) {
        checkTocPosition();
    }, false);

    // 滚动时的处理
    window.addEventListener('scroll', () => {
        const scrollPosition = window.pageYOffset || document.documentElement.scrollTop;

        if (scrollPosition === 0) {
            return;
        }

        if (elements && elements.length > 0) {
            // 重置计数器
            resetHeaderCounters();
            
            // 查找当前可见的标题
            activeElement = Array.from(elements).find((element) => {
                if ((getOffsetTop(element) - scrollPosition) > 0 && 
                    (getOffsetTop(element) - scrollPosition) < window.innerHeight / 2) {
                    return element;
                }
            }) || activeElement;

            // 更新所有目录项的显示
            elements.forEach(element => {
                const id = encodeURI(element.getAttribute('id')).toLowerCase();
                const tocLink = document.querySelector(`.inner ul li a[href="#${id}"]`);
                
                if (tocLink) {
                    updateTocDisplay(element, tocLink);
                    
                    if (element === activeElement) {
                        tocLink.classList.add('active');
                        tocLink.style.textDecoration = "underline";
                        
                        // 确保当前激活的标题在目录中可见
                        const tocContainer = document.querySelector('.toc .inner');
                        const linkOffsetTop = tocLink.offsetTop;
                        const containerHeight = tocContainer.clientHeight;
                        const linkHeight = tocLink.clientHeight;
    
                        // 计算滚动位置,将当前目录项居中显示
                        const scrollPosition = linkOffsetTop - (containerHeight / 2) + (linkHeight / 2);
                        tocContainer.scrollTo({ top: scrollPosition, behavior: 'smooth' });
                    } else {
                        tocLink.classList.remove('active');
                        tocLink.style.textDecoration = "none";
                    }
                }
            });
        }
    }, false);
</script>
{{- end }}

创建assets/css/extended/toc.css,根据阅读内容滚动并加粗相应标题就由其实现。

assets/css/extended/toc.css 88 lines
/*目录侧边栏*/
:root {
    --nav-width: 1380px;
    --article-width: 650px;
    --toc-width: 300px;
}

.toc {
    margin: 0 2px 40px 2px;
    border: 1px solid var(--border);
    background: var(--entry);
    border-radius: var(--radius);
    padding: 0.4em;
}

.toc-container.wide {
    position: absolute;
    height: 100%;
    border-right: 1px solid var(--border);
    left: calc((var(--toc-width) + var(--gap)) * -1);
    top: calc(var(--gap) * 2);
    width: var(--toc-width);
}

.wide .toc {
    position: sticky;
    top: var(--gap);
    border: unset;
    background: unset;
    border-radius: unset;
    width: 100%;
    margin: 0 2px 40px 2px;
}

.toc details summary {
    cursor: zoom-in;
    margin-inline-start: 20px;
    padding: 12px 0;
}

.toc details[open] summary {
    font-weight: 500;
}

.toc-container.wide .toc .inner {
    margin: 0;
}

.active {
    font-size: 110%;
    font-weight: 600;
}

.toc ul {
    list-style-type: circle;
}

.toc .inner {
    margin: 0 0 0 20px;
    padding: 0px 15px 15px 20px;
    font-size: 16px;

    /*目录显示高度*/
    max-height: 83vh;
    overflow-y: auto;
}

.toc .inner::-webkit-scrollbar-thumb {  /*滚动条*/
    background: var(--border);
    border: 7px solid var(--theme);
    border-radius: var(--radius);
}

.toc li ul {
    margin-inline-start: calc(var(--gap) * 0.5);
    list-style-type: none;
}

.toc li {
    list-style: none;
    font-size: 0.95rem;
    padding-bottom: 5px;
}

.toc li a:hover {
    color: var(--secondary);
}
/*目录侧边栏*/

为正文添加章节序号,参考于 初始化 & 设置 PaperMod 主题的基础功能 ,我们在assets/css/common/post-single.css中添加以下内容:

assets/css/common/post-single.css 56 lines
main {
    counter-reset: h1-cnt h2-cnt h3-cnt h4-cnt h5-cnt h6-cnt;
}

.post-content h1 {
    counter-increment: h1-cnt;
    counter-reset: h2-cnt h3-cnt h4-cnt h5-cnt h6-cnt; /* Reset lower levels */
}

.post-content h2 {
    counter-increment: h2-cnt;
    counter-reset: h3-cnt h4-cnt h5-cnt h6-cnt; /* Reset lower levels */
}

.post-content h3 {
    counter-increment: h3-cnt;
    counter-reset: h4-cnt h5-cnt h6-cnt; /* Reset lower levels */
}

.post-content h4 {
    counter-increment: h4-cnt;
    counter-reset: h5-cnt h6-cnt; /* Reset lower levels */
}

.post-content h5 {
    counter-increment: h5-cnt;
    counter-reset: h6-cnt; /* Reset lower levels */
}

.post-content h6 {
    counter-increment: h6-cnt;
}

.post-content h1::before {
    content: counter(h1-cnt) '. ';
}

.post-content h2::before {
    content: counter(h2-cnt) '. ';
}

.post-content h3::before {
    content: counter(h2-cnt) '.' counter(h3-cnt) '. ';
}

.post-content h4::before {
    content: counter(h2-cnt) '.' counter(h3-cnt) '.' counter(h4-cnt) '. ';
}

.post-content h5::before {
    content: counter(h2-cnt) '.' counter(h3-cnt) '.' counter(h4-cnt) '.' counter(h5-cnt) '. ';
}

.post-content h6::before {
    content: counter(h2-cnt) '.' counter(h3-cnt) '.' counter(h4-cnt) '.' counter(h5-cnt) '.' counter(h6-cnt) '. ';
}


同时,由于一号标题有40px大小,推荐从二号标题开始排序。

评论支持

对于评论支持,我选择了 Giscus 。由于本博客也是部署在Github上,在减少额外操作的同时 Giscus 也显得足够的优雅简单。

此处操作参考了 Hugo 博客引入 Giscus 评论系统

  1. Giscus -> 进入配置栏
  2. 配置 -> 仓库 -> 填入仓库名,如 Downmars/Downmars.github.io -> 若不满足条件,分别查看:对应仓库是否公开、giscus app 是否安装、 Discussions 功能在对应仓库是否启用
  3. 配置 -> Discussions 分类 -> 选中Announcements
  4. 配置 -> 启用giscus -> 复制相应字段到配置中

同时需要创建 layouts/partials/comments.html,此处参考了 Hugo + PaperMod + Github Pages 搭建一个完善的个人博客(以 Windows11 为例)

layouts/partials/comments.html 53 lines
{{- /* Comments area start */ -}}
{{- /* to add comments read => https://gohugo.io/content-management/comments/ */ -}}
<div id="tw-comment"></div>
<script>
    // 默认是暗色,根目录下的配置中的主题默认也是暗色
    const getStoredTheme = () => localStorage.getItem("pref-theme") === "light" ? "{{ .Site.Params.giscus.lightTheme }}" : "{{ .Site.Params.giscus.darkTheme }}";
    const setGiscusTheme = () => {
        const sendMessage = (message) => {
            const iframe = document.querySelector('iframe.giscus-frame');
            if (iframe) {
                iframe.contentWindow.postMessage({giscus: message}, 'https://giscus.app');
            }
        }
        sendMessage({setConfig: {theme: getStoredTheme()}})
    }

    document.addEventListener("DOMContentLoaded", () => {
        const giscusAttributes = {
            "src": "https://giscus.app/client.js",
            "data-repo": "{{ .Site.Params.giscus.repo }}",
            "data-repo-id": "{{ .Site.Params.giscus.repoId }}",
            "data-category": "{{ .Site.Params.giscus.category }}",
            "data-category-id": "{{ .Site.Params.giscus.categoryId }}",
            "data-mapping": "{{ .Site.Params.giscus.mapping }}",
            "data-strict": "{{ .Site.Params.giscus.strict }}",
            "data-reactions-enabled": "{{ .Site.Params.giscus.reactionsEnabled }}",
            "data-emit-metadata": "{{ .Site.Params.giscus.emitMetadata }}",
            "data-input-position": "{{ .Site.Params.giscus.inputPosition }}",
            "data-theme": getStoredTheme(),
            "data-lang": "{{ .Site.Params.giscus.lang }}",
            "data-loading": "lazy",
            "crossorigin": "anonymous",
        };

        // 动态创建 giscus script
        const giscusScript = document.createElement("script");
        Object.entries(giscusAttributes).forEach(
                ([key, value]) => giscusScript.setAttribute(key, value));
        document.querySelector("#tw-comment").appendChild(giscusScript);

        // 页面主题变更后,变更 giscus 主题
        const themeSwitcher = document.querySelector("#theme-toggle");
        if (themeSwitcher) {
            themeSwitcher.addEventListener("click", setGiscusTheme);
        }
        const themeFloatSwitcher = document.querySelector("#theme-toggle-float");
        if (themeFloatSwitcher) {
            themeFloatSwitcher.addEventListener("click", setGiscusTheme);
        }
    });
</script>

{{- /* Comments area end */ -}}

网站图标

咱们一个自己的博客肯定得需要有自己的一个网站图标,可以将图标放在/static/images/,如favicon: "/images/blog.png"。我在这里推荐两个网站供大家用来查找符合自己图标。

多语言 & 界面布置

这部分参考来自于 Hugo 多语言博客搭建,如何优雅地管理多语言 md 内容-贤民

我认为对于编写博客的我们来说,以时间的形式来分隔是很好的一种方式来存储以及管理我们的博客,使用统一文件命名规范YYYYMMDD-title.md。此外,有兴趣的可以做一下多语言的准备,即使现在没有推出多语言的打算,之后可以集中找个时间来使用ai工具来统一做一下。

$ tree .
.
├── en
│   ├── archives
│   │   └── archives.md
│   ├── posts
│   │   └── 2025_01_19-hugo_build_1.md
│   └── search
│       └── search.md
└── zh
    ├── archives
    │   └── archives.md
    ├── posts
    │   ├── 2025_01_19-hugo_build_1.md
    │   └── 2025_01_20-hugo_build_2.md
    └── search
        └── search.md

可以参考我的示例结构,在这里我将zh/en/作为post/的直接子目录,这一步需在对应的语言下添加设置对应的文档目录,如contentDir: "content/zh"

我们在此处同时需要设置页面布局,创建zh/archives/archives.mdzh/search/search.md

zh/archives/archives.md 5 lines
---
title: "时间轴"
layout: "archives"
summary: archives
---

zh/search/search.md 5 lines
---
title: "搜索" # in any language you want
layout: "search" # is necessary
summary: "search"
---

其中layout字段指定了这个页面使用的模板,默认使用的是themes\<hugo_theme>\layouts\_default\archives.html&search.html,所以我们使用默认字段searcharchives即可以让归档内容正常显示。

同时,参考 Hugo + PaperMod + Github Pages 搭建一个完善的个人博客(以 Windows11 为例) 搜索需要额外加入如下配置:

hugo.yaml 20 lines
params:
  # 搜索
  fuseOpts: # 个性化配置 refer: https://sonnycalcr.github.io/posts/build-a-blog-using-hugo-papermod-github-pages/#%e9%85%8d%e7%bd%ae%e6%90%9c%e7%b4%a2
      isCaseSensitive: false # 是否大小写敏感
      shouldSort: true # 是否排序
      location: 0
      distance: 1000
      threshold: 0.4
      minMatchCharLength: 0
      # limit: 10 # refer: https://www.fusejs.io/api/methods.html#search
      keys: ["title", "permalink", "summary", "content"]
      includeMatches: true

# ......

outputs:
  home:
    - HTML
    - RSS
    - JSON               # Required for search (搜索功能需要)

接着,我们在配置文件中加入时间轴与搜索的布局即可:

hugo.yaml 13 lines
# Multilingual Support (多语言支持)
languages:
  zh:
    languageCode: "zh-CN"
    languageName: "简体中文"
    contentDir: "content/zh"
    weight: 1
    menu:
      main:
        - identifier: posts
          name: "Posts"
          url: "/posts/"
          weight: 1

渲染配置

代码配置

PaperMod主题使用的代码高亮工具为 Chroma ,可能不太聪明,但是对于我的日常使用暂时没有太大问题。 我在后面换了个代码渲染方式。

Chroma自带的配色方案预览: https://xyproto.github.io/splash/docs/longer/all.html

参考于: 深入探究 Hugo 代码高亮 代码块语法高亮及复制

hugo.yaml 7 lines
markup:
  highlight:
    codeFences: true 
    guessSyntax: true 
    lineNos: true 
    style: dracula 
    lineNumbersInTable: true 

我这里参考了 Hugo PaperMod 主题精装修 的代码渲染方式,在其中选择了 atom-one-dark/light atom-one-dark.css

为了覆盖掉原主题对于代码渲染的设置,我们需要创建assets/css/hljs/an-old-hope.min.css,并且在其中复制进去黑色主题和白色主题的配置,白色主题直接复制进去皆可,黑色主题需要以以下形式进行限定:

assets/css/hljs/an-old-hope.min.css 7 lines
body.dark {
  .hljs {
    color: #abb2bf;
    background: #282c34;
  }
  ...
}

接着,我们需要修改白色与黑色主题的背景色,同时由于我参考的博主使用了Consolas 和霞鹜文楷(注释的中文字体),字体可以从 博主的仓库 中获取。

我们在assets/css/extended/blank.css中添加:

assets/css/extended/blank.css 29 lines
@font-face {
  font-family: "Consolas";
  src: url("/fonts/Consolas.woff2");
}

code {
  font-family: "Consolas", "LXGWWenKaiScreenR";
}

.post-content code {
  margin: auto 4px;
  padding: 4px 6px;
  font-size: 0.8em;
  line-height: 1.5;
  background: var(--code-bg);
}

.post-content pre code {
  display: block;
  margin: auto 0;
  padding: 10px;
  background: var(--hljs-bg) !important;
  color: var(--content);
  border-radius: var(--radius);
  overflow-x: auto;
  word-break: break-all;
  font-family: "Consolas", "LXGWWenKaiScreenR";
  font-size: 15px;
}

并且,在PaperMod中的颜色变量配置assets/css/core/theme-vars.css 中添加配置:

assets/css/core/theme-vars.css 12 lines
/* 省略的内容请拷贝原先主题对应的文件 */
:root {
  ...
  --hljs-bg: #f7f7f7;
  --code-bg: rgb(245, 245, 245);
}

.dark {
  ...
  --hljs-bg: rgb(46, 46, 51);
  --code-bg: rgb(55, 56, 62);
}

我们还会用到 highlight.js 用来添加高亮:

layouts/_default/baseof.html 30 lines
{{- if lt hugo.Version "0.125.7" }}
{{- errorf "=> hugo v0.125.7 or greater is required for hugo-PaperMod to build " }}
{{- end -}}

<!DOCTYPE html>
<html lang="{{ site.Language }}" dir="{{ .Language.LanguageDirection | default "auto" }}">

<head>
+    <link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css">
    {{- partial "head.html" . }}
</head>

<body class="
{{- if (or (ne .Kind `page` ) (eq .Layout `archives`) (eq .Layout `search`)) -}}
{{- print "list" -}}
{{- end -}}
{{- if eq site.Params.defaultTheme `dark` -}}
{{- print " dark" }}
{{- end -}}
" id="top">
    {{- partialCached "header.html" . .Page -}}
    <main class="main">
        {{- block "main" . }}{{ end }}
    </main>
+    {{ partialCached "footer.html" . .Layout .Kind (.Param "hideFooter") (.Param "ShowCodeCopyButtons") -}}
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
<script>hljs.highlightAll();</script>
</body>

</html>

此外,PaperMod 默认的代码复制键的在对应语言下是不同的文字表示复制粘贴,我觉的不够优雅,于是打算使用图标来代替。关于复制符号的配置在layouts/partials/footer.html 中:

layouts/partials/footer.html 8 lines
        copybutton.innerHTML = '{{- i18n "code_copy" | default "copy" }}';

        function copyingDone() {
            copybutton.innerHTML = '{{- i18n "code_copied" | default "copied!" }}';
            setTimeout(() => {
                copybutton.innerHTML = '{{- i18n "code_copy" | default "copy" }}';
            }, 2000);
        }

修改为:

layouts/partials/footer.html 26 lines
        copybutton.innerHTML = `
            <img src="/images/copy.svg" 
                 alt="复制"
                 class="copy-icon"
                 width="16" 
                 height="16">
        `;

        function copyingDone() {
            copybutton.innerHTML = `
                <img src="/images/check.svg" 
                     alt="已复制"
                     class="copied-icon"
                     width="16" 
                     height="16">
            `;
            setTimeout(() => {
                copybutton.innerHTML = `
                    <img src="/images/copy.svg" 
                         alt="复制"
                         class="copy-icon"
                         width="16" 
                         height="16">
                `;
            }, 2000);
        }

我这里在存放的图片位于static/images/,大家可以选择自己的路径。由于编译为静态博客之后,static/images/变为public/images/,同时以public/当作根目录,所以我在代码中使用/images/check.svg&copy.svg

接下来,我们配置复制图标样式,在assets/css/extend.css中添加以下代码:

assets/css/extend.css 20 lines
/*copy_button*/
.copy-code {
    background: transparent;
    border: none;
    cursor: pointer;
    padding: 4px;
    position: absolute;
    right: 10px;
    top: -2px;
}

.copy-code img {
    opacity: 0.6;
    transition: opacity 0.3s, filter 0.3s; /* 添加 filter 过渡效果 */
}

.copy-code:hover img {
    opacity: 1;
    filter: invert(100%); /* 颜色翻转效果 */
}

我这里使用的是底色为黑色的svg图标,所以我这里做了反色处理。

接下来我们配置代码折叠,对于过长的代码段,十分影响阅读者的感受,参考于 Hugo PaperMod 主题精装修 ,我们需要在layouts/shortcodes/collapse.html中加入以下内容:

layouts/shortcodes/collapse.html 41 lines
{{/* 参数处理逻辑 */}}
{{ $threshold := default 15 (.Get "collapseThreshold") | int }}
{{ $forceCollapse := eq (.Get "forceCollapse") "true" }}
{{ $openByDefault := eq (.Get "openByDefault") "true" }}

{{/* 内容处理逻辑 */}}
{{ $rawContent := .Inner }}
{{ $content := $rawContent | markdownify }}

{{/* 精确行数计算(排除代码块标记和空行) */}}
{{ $cleanedContent := replaceRE `(?s)<pre.*?>\n?` "" $content }}
{{ $cleanedContent := replaceRE `(?s)</pre>\n?` "" $cleanedContent }}
{{ $lines := split $cleanedContent "\n" }}
{{ $lineCount := -1 }}
{{ range $line := $lines }}
  {{ if ne (trim $line " ") "" }}
    {{ $lineCount = add $lineCount 1 }}
  {{ end }}
{{ end }}

{{/* 自动折叠判断逻辑 */}}
{{ $shouldOpen := cond $forceCollapse 
  false 
  (or $openByDefault (lt $lineCount $threshold)) 
}}

{{/* 错误处理 */}}
{{ if not (.Get "summary") }}
  {{ warnf "missing value for param 'summary': %s" .Position }}
{{ end }}


<p><details class="custom-collapse" {{ if $shouldOpen }}open{{ end }}>
  <summary markdown="span">
    <span>{{ .Get "summary" | markdownify }}</span>
    <span class="line-count">{{ $lineCount }} lines</span>
  </summary>
  <div class="content">
    {{ $content }}
  </div>
</details></p>

额外配置

正文宽度

我们复制对应目录assets/css/core/theme-vars.css,并修改以下行:

assets/css/core/theme-vars.css 2 lines
root:
    --main-width: 740px;

文章封面图移至侧边

当我们文章存在封面的时候,在archives中文章的排布中,图片占了大部分空间,十分影响我们的观感。通过参考: Hugo博客文章封面图片缩小并移到侧边 | PaperMod主题 ,我们可以这样配置:

layouts/_default/list.html 33 lines
<article class="{{ $class }}">
-  {{- $isHidden := (.Param "cover.hiddenInList") | default (.Param "cover.hidden") | default false }}
-  {{- partial "cover.html" (dict "cxt" . "IsSingle" false "isHidden" $isHidden) }}
+  <div class="post-info">
  <header class="entry-header">
    <h2 class="entry-hint-parent">
      {{- .Title }}
      {{- if .Draft }}
      <span class="entry-hint" title="Draft">
        <svg xmlns="http://www.w3.org/2000/svg" height="20" viewBox="0 -960 960 960" fill="currentColor">
          <path
            d="M160-410v-60h300v60H160Zm0-165v-60h470v60H160Zm0-165v-60h470v60H160Zm360 580v-123l221-220q9-9 20-13t22-4q12 0 23 4.5t20 13.5l37 37q9 9 13 20t4 22q0 11-4.5 22.5T862.09-380L643-160H520Zm300-263-37-37 37 37ZM580-220h38l121-122-18-19-19-18-122 121v38Zm141-141-19-18 37 37-18-19Z" />
        </svg>
      </span>
      {{- end }}
    </h2>
  </header>
  {{- if (ne (.Param "hideSummary") true) }}
  <div class="entry-content">
    <p>{{ .Summary | plainify | htmlUnescape }}{{ if .Truncated }}...{{ end }}</p>
  </div>
  {{- end }}
  {{- if not (.Param "hideMeta") }}
  <footer class="entry-footer">
    {{- partial "post_meta.html" . -}}
  </footer>
  {{- end }}
+  </div>
+  {{- $isHidden := (.Param "cover.hiddenInList") | default (.Param "cover.hidden") | default false }}
+  {{- partial "cover.html" (dict "cxt" . "IsSingle" false "isHidden" $isHidden) }}
  <a class="entry-link" aria-label="post link to {{ .Title | plainify }}" href="{{ .Permalink }}"></a>
</article>
{{- end }}

assets/css/common/post-entry.css 20 lines
/* F2 make the cover in the side on blogs page */
.post-entry {
  display: flex;
  flex-direction: row;
  align-items: center;
}

.post-info {
  display: inline-block;
  overflow: hidden;
  width: 90%;
}

.post-entry .entry-cover {
  overflow: hidden;
  padding-right: 18px;
  height: 80%;
  width: 40%;
  margin-bottom: unset;
}

字体配置

我已经忍这个默认字体很久了,现在我们就在这里干掉他。我们在这里选择的中文是 霞鹜文楷 ,英文是 Apple 的字体 SF Pro Text Regular 来渲染,方法参考于: Hugo PaperMod 主题精装修

assets/css/extended/blank.css中加入:

assets/css/extended/blank.css 19 lines
@font-face {
  font-family: "LXGWWenKaiScreenR";
  src: url("/fonts/lxgwwenkaiscreen.subset.v1.235.standard.woff2");
}

/* https://www.webfontfree.com/cn/download/SFProText-Regular */
@font-face {
  font-family: "SFProText-Regular";
  src: url("/fonts/SFProText-Regular.woff2");
}

body {
  font-family: "SFProText-Regular", "LXGWWenKaiScreenR";
  font-size: 16px;
  line-height: 1.6;
  word-break: break-word;
  background: var(--theme);
  font-display: swap;
}

字体可以从我上面提及博主的 github仓库 处获取。

知识共享协议

参考于: Hugo+PaperMod 双语博客搭建 Home-Info+Profile Mode Hugo ʕ•ᴥ•ʔ Bear Blog

我们在 Creative Commons许可证 中选择使用的知识共享协议,我这里选择的是CC-BY-NC-4协议,并添加一个协议模组来自动呈现:

首先创建文件data/licenses.html并添加

data/licenses.html 14 lines
# data/licenses.yml
CC-BY-NC-4.0:
  name: "CC BY-NC 4.0"
  url: "https://creativecommons.org/licenses/by-nc/4.0/"
  icons: &cc_icons  # 锚点复用图标列表
    - cc.svg
    - by.svg
    - nc.svg

# 其他协议
CC-BY-SA-4.0:
  name: "CC BY-SA 4.0"
  url: "https://creativecommons.org/licenses/by-sa/4.0/"
  icons: *cc_icons  # 复用图标列表

其次,创建文件layouts/partials/license.html并添加:

layouts/partials/license.html 35 lines
<hr> <!-- 添加分割线 -->
{{ $license := index site.Data.licenses .Params.license }}
{{ if $license }}
<div class="license-declaration" vocab="https://schema.org/" typeof="CreativeWork">
  {{/* 自动检测主题模式 */}}
  {{ $theme := "light" }}
  {{ if or (eq .Site.Params.colorTheme "dark") (in .Site.Params.colorTheme "auto") }}
    {{ $theme = "dark" }}
  {{ end }}
  
  <meta property="name" content="{{ .Title }}">
  <link property="copyrightHolder" href="{{ .Site.BaseURL }}#author" />
  <p>
    <span property="license" content="{{ $license.url }}">本文采用</span>
    <a 
      href="{{ $license.url }}" 
      target="_blank" 
      rel="license noopener noreferrer"
      class="license-badge"
      data-theme="{{ $theme }}"
    >
      {{ $license.name }}
      {{ range $icon := $license.icons }}
        <img 
          src="https://mirrors.creativecommons.org/presskit/icons/{{ $icon }}" 
          alt="{{ $icon | replaceRE `\.svg$` `` | upper }} 图标"
          class="license-icon"
          loading="lazy"
          decoding="async"
        >
      {{ end }}
    </a>
  </p>
</div>
{{ end }}

接下来,创建文件assets/extended/custom.css并添加:

assets/extended/custom.css 60 lines
/* licenses.css */
.license-badge {
  --bg-light: #f8f9fa;
  --bg-dark: #2b2d32;
  --text-light: #212529;
  --text-dark: #f8f9fa;
  
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 16px;
  border-radius: 6px;
  font-size: 0.9em;
  transition: all 0.3s ease;
  
  /* 默认浅色主题 */
  background: var(--bg-light);
  color: var(--text-light);
  border: 1px solid rgba(0,0,0,0.1);
}

.license-icon {
  height: 24px;
  width: auto;
  transition: filter 0.3s ease;
}

/* 深色主题检测 */
@media (prefers-color-scheme: dark) {
  .license-badge:not([data-theme="light"]) {
    background: var(--bg-dark);
    color: var(--text-dark);
    border-color: rgba(255,255,255,0.1);
    
    & .license-icon {
      filter: invert(1) hue-rotate(180deg); /* 更自然的反色 */
    }
  }
}

/* 强制深色模式 */
.license-badge[data-theme="dark"] {
  background: var(--bg-dark);
  color: var(--text-dark);
  border-color: rgba(255,255,255,0.1);
  
  & .license-icon {
    filter: invert(1) hue-rotate(180deg);
  }
}

/* 悬停效果 */
.license-badge:hover {
  transform: translateY(-1px);
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  
  &[data-theme="dark"]:hover {
    box-shadow: 0 2px 8px rgba(255,255,255,0.1);
  }
}

最后,在archetypes/default.md中添加以下代码段即可实现一个自动添加的开源协议呈现模组。

archetypes/default.md 1 lines
license: "CC-BY-NC-4.0"  # 你对应使用的协议

呈现效果如下:

CC-BY-NC-4
CC-BY-NC-4

但是,由于我对于前端不是很了解,黑夜模式下的样式调整未能正确实现,待解决。

超链接

hugo的默认超链接是以当前页面打开,这对于我们的阅读体验是很糟糕的,参考于 Hugo 设置外部链接用新窗口打开 ,我们将外部链接使用新窗口打开并在链接后面添加一个小箭头,而本博客的内容使用当前窗口打开并不添加后缀:

layouts/_default/_markup/render-link.html 10 lines
<a href="{{ .Destination | safeURL }}" 
   {{ with .Title }} title="{{ . }}"{{ end }}
   {{ if strings.HasPrefix .Destination "http" }} 
       target="_blank" rel="noopener" 
   {{ end }}>
   {{ .Text | safeHTML }}
   {{- if strings.HasPrefix .Destination "http" -}}
       <span class="external-link">↗</span>
   {{- end -}}
</a>  


同时在添加小尖头样式:

assets/extended/custom.css 7 lines
.external-link {
    display: inline-block;
    margin-left: 0.2em;
    font-size: 0.8em;
    text-decoration: none;
    vertical-align: super;
}  

外部链接: 我的github主页
内部链接: Hugo博客搭建_基础 😊

修改时间

原本主题并没有显示「修改时间」的功能,我在这里参考了 Hugo PaperMod 主题精装修

layouts/partials/post_meta.html中添加以下内容,参考的博客中$scratch.Add错误被包裹在()中,并且行末还有一个多余的$,我在此做了修改:

layouts/partials/post_meta.html 5 lines
{{- if (.Param "ShowLastMod") -}}
    {{- if ne (.Lastmod.Format "2006-01-02") (.Date.Format "2006-01-02") -}}
        {{- $scratch.Add "meta" (slice (printf "Updated:&nbsp;%s" (.Lastmod.Format (.Site.Params.dateFormat | default "January 2, 2006")))) -}}
    {{- end -}}
{{- end -}}

并且,在hugo.yaml中加入:

hugo.yaml 2 lines
params:
  showLastMod: true

同时,我们在archetypes/default.md中的配置如下:

archetypes/default.md 5 lines
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ .Date }}
lastmod: {{ .Date }}
---

等我过几天看看这个效果咋样。

MarginNote

在这里十分感谢 Yunpeng Tai 的博客,我从上面学习和摘选了很多内容在本文中。

我在此处对于样式做了部分修改,在assets/extended/custom.css

assets/extended/custom.css 75 lines

.sidenote {
  float: right;
  clear: right;
  position: relative;
  margin-top: 1rem;
  max-width: 300px;
  padding: 8px 16px; /* 增加内边距以便有足够空间 */
  border: 2px solid transparent; /* 默认透明边框 */
  border-radius: var(--radius); /* 圆角 */
  box-shadow: 0 2px 4px var(--sidenote-shadow1); /* 默认轻微阴影 */
  transition: all 0.3s ease; /* 增加平滑过渡效果 */
}

/* Wide viewport */
@media (min-width: 1400px) {
    .sidenote {
        float: right;
        clear: right;
        margin-right: -20vw;
        text-align: left;
        top: -3rem;
        width: 20vw;
        margin-top: 1rem;
    }
}

/* Narrow viewport */
@media (max-width: 1400px) {
    .sidenote {
        float: right;
        text-align: left;
        width: 100%;
        margin: 1rem 0;
        padding-left: 15%;
    }
}

/* 定义数字编号的外观 */
.sidenote-number {
  counter-increment: sidenote-counter;
  position: relative;
}

/* 给每个sidenote添加#符号 */
.sidenote::before {
  content: "# ";
  position: relative;
  font-size: 0.9em;
  font-weight: 700;
  color: red; /* 默认红色 */
  transition: all 0.3s ease; /* 增加过渡效果 */
}

/* 给sidenote-number添加后缀# */
.sidenote-number::after {
  content: "#";
  vertical-align: super;
  font-size: 0.8em;
  font-weight: 700;
  color: #409dff; /* 默认蓝色 */
  transition: all 0.3s ease; /* 增加过渡效果 */
}

/* 悬停时,数字#和注释的高亮效果 */
.sidenote-number:hover::after {
  color: red; /* 悬停时改变#符号的颜色 */

}

/* 悬停时,增加sidenote的背景和阴影 */
.sidenote-number:hover .sidenote {
  background-color: var(--sidenote-bg-hover); /* 悬停时背景色变化 */
  box-shadow: 0 4px 8px var(--sidenote-shadow2); /* 强化阴影效果 */
}

同时,对于shortcode也有相应的修改,在layouts/shortcodes/sidenote.html中添加以下内容:

layouts/shortcodes/sidenote.htm 1 lines
<span class="sidenote-number"><small class="sidenote">{{ .Inner | markdownify }}</small></span>

这是示例 这是示例的侧边注解

盘古之白

「盤古之白」 一文中讨论到,所有的中文字和半形的英文、數字、符號之間应该存在的空白,被漢學家稱為「盤古之白」,因為它劈開了全形字和半形字之間的混沌。

我们需要在layouts/partials/extend_footer.html中加入:

layouts/partials/extend_footer.html 18 lines
{{- $highlight := resources.Get "js/pangu.min.js" -}}
<script>
  (function (u, c) {
    var d = document,
      t = "script",
      o = d.createElement(t),
      s = d.getElementsByTagName(t)[0];
    o.src = u;
    if (c) {
      o.addEventListener("load", function (e) {
        c(e);
      });
    }
    s.parentNode.insertBefore(o, s);
  })("{{ $highlight.RelPermalink }}", function () {
    pangu.spacingPage();
  });
</script>

同时,需要创建assets/js/pangu.min.js,网站加载时,盘古之白自动加载,大家可以从此处下载。

Blockquote

参考于: Hugo PaperMod 主题精装修

为什么要演奏春日影!

layouts/shortcodes/quote.html 加入以下内容:

layouts/shortcodes/quote.html 8 lines
<blockquote class="quote{{ range .Params }} {{ . }}{{ end }}">
    {{- $content := .Inner | markdownify -}}
    {{- if not (strings.HasPrefix $content "<p>") -}}
        {{ printf "<p>%s</p>" $content | safeHTML }}
    {{- else -}}
        {{ $content }}
    {{- end -}}
</blockquote>

我们在assets/css/extended/quote.css中加入以下内容:

assets/css/extended/quote.css 32 lines
blockquote.quote {
  position: relative;
  margin: 1em auto;
  padding-left: 3em;
  border: none;
}

blockquote.quote::before {
  position: absolute;
  left: 0;
  content: "“";
  font-size: 3em;
  font-weight: bold;
  line-height: 1;
}

blockquote.quote-copyright {
  position: relative;
  margin: 2em auto;
  padding-left: 3em;
  border: none;
  background-color: aliceblue;
}

blockquote.quote-copyright::before {
  position: absolute;
  left: 0;
  content: "“";
  font-size: 3em;
  font-weight: bold;
  line-height: 1;
}

Admonition

Warning
注意查看博客修改时间,博客内容可能过时!
在博客中往往需要我们在一些地方给予读者额外需要注意的信息,这时候我们可以使用Admonition。我们需要添加一个支持Admonition(类似提示、警告、注意等区块)的 Shortcode:

layouts/shortcodes/admonition.html 26 lines
{{- $type := .Get "type" | default "note" -}}
{{- $title := .Get "title" | default (humanize $type) -}}
{{- $icon := .Get "icon" -}}
{{- $collapsible := .Get "collapsible" | default false -}}

{{/* 默认图标映射(可自定义) */}}
{{- $defaultIcons := dict 
  "note" "fas fa-info-circle"
  "tip" "fas fa-lightbulb"
  "warning" "fas fa-exclamation-triangle"
  "danger" "fas fa-skull-crossbones"
-}}
{{- if not $icon -}}
  {{- $icon = index $defaultIcons $type -}}
{{- end -}}

<div class="admonition {{ $type }} {{ if $collapsible }}collapsible{{ end }}">
  <div class="admonition-header">
    {{ if $icon }}<i class="{{ $icon }}"></i>{{ end }}
    <span>{{ $title }}</span>
    {{ if $collapsible }}<i class="toggle-icon fas fa-chevron-down"></i>{{ end }}
  </div>
  <div class="admonition-content">
    {{ .Inner | markdownify }}
  </div>
</div>

同时,我们可以在为其添加css样式:

assets/extended/admonition.css 75 lines
/* 基础变量 */
:root {
  --admonition-border-width: 4px;
  --admonition-radius: 8px;
  --admonition-shadow: 0 3px 10px rgba(0,0,0,0.05);
  --transition-speed: 0.3s;
}

.admonition {
  margin: 2rem 0;
  border-left: var(--admonition-border-width) solid;
  border-radius: var(--admonition-radius);
  background: white;
  box-shadow: var(--admonition-shadow);
  transition: transform var(--transition-speed) ease, box-shadow var(--transition-speed) ease;
}

/* 悬停动画 */
.admonition:hover {
  transform: translateY(-2px);
  box-shadow: 0 6px 15px rgba(0,0,0,0.1);
}

/* 头部样式 */
.admonition-header {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  padding: 0.8rem 1.2rem 0.8rem 1.2rem;
  font-size: 1.2rem; /* 标题字体大小 */
  font-weight: 600;
  border-radius: var(--admonition-radius) var(--admonition-radius) 0 0;
}

/* 内容区域 */
.admonition-content {
  font-size: 1rem; /* 内容区域字体大小 */
  font-weight: normal;
  padding: 0rem 1.2rem 0.8rem 1.2rem;
  line-height: 1.6;
  color: rgba(0,0,0,0.8);
}

/* 类型配色(更柔和的现代色) */
.admonition.note {
  border-color: #4A90E2;
  background: linear-gradient(to right, #f8fcff 1%, white 10%);
}
.admonition.tip {
  border-color: #00C781;
  background: linear-gradient(to right, #f2fff9 1%, white 10%);
}
.admonition.warning {
  border-color: #FFB800;
  background: linear-gradient(to right, #fff9e6 1%, white 10%);
}
.admonition.danger {
  border-color: #FF4757;
  background: linear-gradient(to right, #fff6f5 1%, white 10%);
}

/* 折叠功能 */
.admonition.collapsible .admonition-content {
  display: none;
}
.admonition.collapsible.active .admonition-content {
  display: block;
}
.admonition.collapsible .toggle-icon {
  margin-left: auto;
  transition: transform var(--transition-speed) ease;
}
.admonition.collapsible.active .toggle-icon {
  transform: rotate(180deg);
}

为了添加折叠功能,我们需要为其添加JS文件:

assets/js/admonition.js 6 lines
document.querySelectorAll('.admonition.collapsible').forEach(admonition => {
  const header = admonition.querySelector('.admonition-header');
  header.addEventListener('click', () => {
    admonition.classList.toggle('active');
  });
});

由于我们使用了 Fonts Awesome 作为我们的图标支持,还需要导入Fonts Awesome的样式文件,我们这边选择CDN导入。并且在同一文件中导入折叠的JS:

layouts/_default/baseof.html 35 lines
{{- if lt hugo.Version "0.125.7" }}
{{- errorf "=> hugo v0.125.7 or greater is required for hugo-PaperMod to build " }}
{{- end -}}

<!DOCTYPE html>
<html lang="{{ site.Language }}" dir="{{ .Language.LanguageDirection | default "auto" }}">

<head>
    <link rel="stylesheet" href="{{ "an-old-hope.min.css" | relURL }}">
    {{- partial "head.html" . }}
+    <!-- 添加 Font Awesome CDN 链接 -->
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head>

<body class="
{{- if (or (ne .Kind `page` ) (eq .Layout `archives`) (eq .Layout `search`)) -}}
{{- print "list" -}}
{{- end -}}
{{- if eq site.Params.defaultTheme `dark` -}}
{{- print " dark" }}
{{- end -}}
" id="top">
    {{- partialCached "header.html" . .Page -}}
    <main class="main">
        {{- block "main" . }}{{ end }}
    </main>
    {{ partialCached "footer.html" . .Layout .Kind (.Param "hideFooter") (.Param "ShowCodeCopyButtons") -}}
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
<script>hljs.highlightAll();</script>
+<!-- admonition -->
+{{ $js := resources.Get "js/admonition.js" | minify | fingerprint }}
+<script src="{{ $js.RelPermalink }}" defer></script>
</body>

</html>

友链

友链也是博客中不得不品的一个环节,我在这里参考的是 sulv-hugo-papermod|xyming108 ,当鼠标浮动到友链所在的位置,头像就会开始旋转并放大。首先,创建一个friend的shortcode:

layouts/shortcodes/friends.html 13 lines
{{- if .IsNamedParams -}}
<a target="_blank" href={{ .Get "url" }} title={{ .Get "name" }} class="friendurl">
  <div class="frienddiv">
    <div class="frienddivleft">
      <img class="myfriend" src={{ .Get "logo" }} />
    </div>
    <div class="frienddivright">
      <div class="friendname">{{- .Get "name" -}}</div>
      <div class="friendinfo">{{- .Get "word" -}}</div>
    </div>
  </div>
</a>
{{- end }}

这边参考的是 旋转的友链|Yunpeng Tai ,我们需要添加对应的css来美化一下。

assets/css/extended/friends.css 99 lines
.friendurl {
  text-decoration: none !important;
  color: black;
  box-shadow: none !important;
}

.myfriend {
  width: 56px !important;
  height: 56px !important;
  border-radius: 50% !important;
  padding: 2px;
  margin-top: 20px !important;
  margin-left: 14px !important;
  background-color: #fff;
}

.frienddiv {
  overflow: auto;
  height: 100px;
  width: 49%;
  display: inline-block !important;
  border-radius: 5px;
  background: none;
  -webkit-transition: all ease-out 0.3s;
  -moz-transition: all ease-out 0.3s;
  -o-transition: all ease-out 0.3s;
  transition: all ease-out 0.3s;
}

.dark .frienddiv:hover {
  background: var(--code-bg);
}

.frienddiv:hover {
  background: var(--theme);
  transition: transform 1s;
  webkit-transform: scale(1.1);
  -moz-transform: scale(1.2);
  -ms-transform: scale(1.2);
  -o-transform: scale(1.2);
  transform: scale(1.1);
}

.frienddiv:hover .frienddivleft img {
  transition: 0.9s !important;
  -webkit-transition: 0.9s !important;
  -moz-transition: 0.9s !important;
  -o-transition: 0.9s !important;
  -ms-transition: 0.9s !important;
  transform: rotate(360deg) !important;
  -webkit-transform: rotate(360deg) !important;
  -moz-transform: rotate(360deg) !important;
  -o-transform: rotate(360deg) !important;
  -ms-transform: rotate(360deg) !important;
}

.frienddivleft {
  width: 92px;
  float: left;
  margin-right: -5px;
}

.frienddivright {
  margin-top: 18px;
  margin-right: 18px;
}

.friendname {
  text-overflow: ellipsis;
  font-size: 100%;
  margin-bottom: 5px;
  color: var(--primary);
}

.friendinfo {
  text-overflow: ellipsis;
  font-size: 70%;
  color: var(--primary);
}

@media screen and (max-width: 600px) {
  .friendinfo {
    display: none;
  }
  .frienddivleft {
    width: 84px;
    margin: auto;
  }
  .frienddivright {
    height: 100%;
    margin: auto;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .friendname {
    font-size: 18px;
  }
}

在我们能够使用shortcode来显示友链了,我们还需要创建一个页面来放置我们的友链。
首先,需要在hugo.yaml中添加以下内容:

hugo.yaml 8 lines
languages:
  zh:
    menu:
      main:
+        - identifier: friends
+          name: "友链"
+          url: "/friends/"
+          weight: 4

其次,我们在创建对应位置的friends的导览页内容:

$ mkdir content/zh/friends/
$ vim content/zh/friends/index.md  

在其中添加:

content/zh/friends/index.md 11 lines
---
title: "友链"
layout: "friends"
summary: "这是我的友链页面"
---
{{</* friend 
    name="Downmars" 
    url="https://downmars.github.io/zh/posts/" 
    logo="https://raw.githubusercontent.com/Downmars/images-PicGo/main/img/miku.jpg"
    word="Enjoy your life!" 
*/>}}

这边的友链格式为:{{< friend name="downmars" url="https://downmars.github.io/zh/posts/" logo="https://raw.githubusercontent.com/downmars/images-picgo/main/img/miku.jpg" word="enjoy your life!" >}},上述的/* */是为了能够顺利转义我的shortcode而不被markdown转义为友链。

接着,我们为这个导览页添加html样式:

layouts/_default/friends.html 14 lines
{{ define "main" }}
<main class="main">
    <article class="friend-container">
        <header class="page-header">
            <h1>{{ .Title }}</h1>
            {{ with .Params.summary }}<p class="summary">{{ . }}</p>{{ end }}
        </header>
        
        <section class="friend-grid">
            {{ .Content }}
        </section>
    </article>
</main>
{{ end }}

这样我们应该就可以在页面的对应位置看到我们的友链链接了。

Mermaid

大家如果有一些复杂的关系图,需要一些示意图、流程图的地方,那么Mermaid可能会是你需要的。我们能够以代码的格式书写我们的Mermaid图,这能够帮我们以纯文本的方式保存我们的文件内容,这对于内容的传播性、开放性和可读性都有很好的诠释。这里的创建方法参照了 Mermaid图|Yunpeng Tai

layouts/_default/_markup/render-codeblock-mermaid.html 5 lines
<!-- 因为正常写会有 ```meraid ... ``` -->
<pre class="mermaid">
   {{- .Inner | htmlEscape | safeHTML }}
</pre>
{{ .Page.Store.Set "hasMermaid" true }}

layouts/partials/mermaid.html 66 lines
{{ if .Page.Store.Get "hasMermaid" }}
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
<script>
  const elementCode = ".mermaid";
  const loadMermaid = function (theme) {
    mermaid.initialize({ theme });
    mermaid.init({
      theme,
      themeVariables: { // 这里设置字体跟正文一致
        fontFamily: ["SFProText-Regular", "LXGWWenKaiScreenR"]
      }}, document.querySelectorAll(elementCode));
  };
  const saveOriginalData = function () {
    return new Promise((resolve, reject) => {
      try {
        var els = document.querySelectorAll(elementCode),
          count = els.length;
        els.forEach((element) => {
          element.setAttribute("data-original-code", element.innerHTML);
          count--;
          if (count == 0) {
            resolve();
          }
        });
      } catch (error) {
        reject(error);
      }
    });
  };
  const resetProcessed = function () {
    return new Promise((resolve, reject) => {
      try {
        var els = document.querySelectorAll(elementCode),
          count = els.length;
        els.forEach((element) => {
          if (element.getAttribute("data-original-code") != null) {
            element.removeAttribute("data-processed");
            element.innerHTML = element.getAttribute("data-original-code");
          }
          count--;
          if (count == 0) {
            resolve();
          }
        });
      } catch (error) {
        reject(error);
      }
    });
  };

  saveOriginalData().catch(console.error);
  // 不要用 localStorage.getItem("pref-theme"),因为有些时候会为 null
  let isdark = document.body.className.includes("dark");
  if (isdark) {
    resetProcessed().then(loadMermaid("dark")).catch(console.error);
  } else {
    resetProcessed().then(loadMermaid("neutral")).catch(console.error);
  }
  document.getElementById("theme-toggle").addEventListener("click", () => {
    resetProcessed();
    document.body.className.includes("dark")
      ? loadMermaid("neutral")
      : loadMermaid("dark").catch(console.error);
  });
</script>
{{ end }}

layouts/_default/single.html 4 lines
<article>
  <!-- 省略上面的 -->
+  {{- partial "mermaid.html" . }}
</article>

Shortcodes大赏

hugo采用markdown的格式进行内容创作,但是当我们需要一些特殊功能的时候,markdown并不能满足我们的需求。我们在这个时候可能需要插入一些html代码,这对于整个markdown的格式与可读性都是很糟糕的。幸运的是,hugo给我们提供了简码(Shortcodes)功能,在我们准备好对应的简码模板与简码,这能够在使用简码的时候,将hugo自动转换为html语言,我们就能够使用很简码的样式来实现需要的特殊功能。

关于Shortcodes的相关知识可以参考 Shortcodes templates ,大家可以自行前往查看。

图像放大

{{< figure src="/path/to/image.jpg" alt="A beautiful image" title="Image" caption="This is a caption for the image" align="center" width=600px height=300px >}}

A beautiful landscape
Sonny_boy

This is a caption for the image

自动折叠代码

{{< collapse summary="test" >}} {代码段} {{< /collapse >}}

test 1 lines
test

侧边注解

这是示例 {{< sidenote >}} 这是示例的侧边注解 {{< /sidenote >}}

这是示例 这是示例的侧边注解aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

引用

{{< quote >}} {引用段} {{< /quote >}}

为什么要演奏春日影!

转义渲染

参考于: 如何在代码块里内嵌 HUGO 的简码 (SHORTCODES) | 消夏錄 ,由于Shortcodes代码会被hugo直接转义为对应的功能,所以我们需要将Shortcodes的括号内加入/* */防止转义。我们需要在markdown中输入:

{{</* myshortcode */>}}

渲染结果:

{{< myshortcode >}}

Asciinema

{{< asciinema ID >}}

Admonition

{{< admonition type=“danger” title=“严重警告!” collapsible=“true” >}}
可折叠的警告区块,点击头部展开/收起
支持 Markdown 和自动图标⚡
{{< /admonition >}}

Note
这是一个记录。
Tip
这是一个提示!
Warning
警告警告⚠️
严重警告!
可折叠的警告区块,点击头部展开/收起
支持 Markdown 和自动图标⚡

Mermaid

我在这边单开了一篇关于Mermaid的内容,可以参考此内容: Mermaid使用例

总结

关于hugo的配置暂时就告一段落了,之后可能会有所增减,希望能帮助到大家。😄