Hugo | 个人向博客样式修订手册

前言

本文整理了一些可能对其他人有用的博客样式修改,并尽可能保证文章可读性,如有表意不清,或表达未尽部分,可参阅本博客主题代码hugo-theme-stack,或通过评论区留言。

软件篇

如果你也不喜欢使用 VS Code。

原谅我把这部分放在篇首。我对微软系产品颇有微词,非要说就是相性不合,因为编辑博客代码的频率不高,捏着鼻子用了一年多,终于忍无可忍。认识的许多写博客的朋友使用 VS Code 的原因是口口相传,如果也对使用体验不是那么满意,我觉得也可以尝试一下新的选择。

我平日使用 JetBrains 系列产品,但一直记得他家是付费软件,所以不在我的考虑范围内。这次偶然看了一下,发现他们的前端 IDE WebStorm 是非商用免费的。下载之后很丝滑地申请了一个非商用许可证,使用体验终于舒畅了。这让我不禁怀疑,过去一年和 VS Code 的虐恋究竟算什么!

JetBrains 常用的非商业免费产品有 PyCharm Community(用于 Python)和上文提到的 WebStorm。如果是学生,还可以通过 edu 邮箱或学生证申请教育许可证,在毕业之前免费使用他们的全系产品。

我爱用他们家产品的理由是:开箱即用,功能强大,全局检索、代码补全和自动缩进都很好用,插件库丰富,操作逻辑符合我的日常使用习惯。总而言之,如果你更喜欢开箱即用的集成开发环境,不想将时间花在繁杂的配置项中,我觉得 WebStorm 是一个很好的选择;反之,如果你更喜欢轻量级的代码编辑器,那么 VS Code 或许更适合你。

常用快捷键可见官方文章十大必会 WebStorm 快捷键WebStorm keyboard shortcuts。我只介绍最好用的:

Search Everywhere:双击 shift 进行全局搜索,从文件名到变量名到文字,都可以快速跳转。

如果想使用它来修改 Hugo 博客,可以安装以下插件获得更佳体验:

Hugo Integration 插件使用说明:

  1. 安装插件后双击 shift,输入 Edit Configurations 并选择:

  2. 点击右上角加号选择Hugo,按照图示填写配置(只需要加一个-D的参数):

  1. 点击右上角三角形或使用 ⌘Cmd+R 一键运行

另外在图上附上了一些图形化 Git 相关内容的位置。

总之,我真的挺喜欢用他们家软件的,有兴趣的朋友可以用用看,反正并不要钱呢……

RSS 篇

备注:本部分基于 hugo-theme-stack 的 rss.xml 编写。

新增 RSS

Hugo 默认使用 /index.xml 作为 RSS 输出,部分博客主题(如 hugo-theme-stack)原生支持 RSS。如果存在不支持的主题,可以考虑新建 <theme_folder>/layouts/_default/rss.xml,内容可以使用本博客的 rss.xml 或其它博客主题的内容。

全文 RSS

hugo-theme-stack 原生支持全文 RSS,默认打开,并通过配置项开关,相关代码如下:

25        {{- $content := safeHTML (.Summary | html) -}}
26        {{- if .Site.Params.rssFullContent -}}
27            {{- $content = safeHTML (.Content | html) -}}
28        {{- end -}}

意思是:默认输出文章摘要,如果 rssFullContent 配置项为开,输出全文。

对于其它主题:

  1. 如果是原生不支持 RSS 的主题,在新建 rss.xml 文件后,不想通过配置开关全文RSS,可以考虑上述几行代码改为:
25        {{- $content := safeHTML (.Content | html) -}}
  1. 如果是原生支持 RSS ,但仅显示摘要的主题,可以在 rss.xml 找到 .Summary 的输出部分,将其改为 .Content

排除部分文章

部分博客主题的 RSS 默认会输出包括 About/Link 页面在内的所有文章,相关代码如下:

3{{- $pages := slice -}}
4{{- if or $.IsHome $.IsSection -}}
5{{- $pages = $pctx.RegularPages -}}
6{{- else -}}
7{{- $pages = $pctx.Pages -}}
8{{- end -}}
9{{- $pages := where $pages "Params.hidden" "!=" true -}}

意思是:从 RegularPages/Pages 下获取文章,并排除标签为 hidden 的文章。

如果想要排除博文以外的文章,有两种解决方案:

  1. 所有博文在单一文件夹下(如 posts )的情况,可将上述代码替换为:
1
{{ $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections }}
  1. 手动将不想展示的博文设为 hidden: true

短代码友好的 RSS 输出

注:本部分需要首先支持全文 RSS。

很多短代码在 RSS 输出中不够友好(如 NeoDB 卡片),对此,可以在 shortcode 目录下建立同名 <name>.rss.xml 文件,替代 <name>.html 文件的输出。

最简单的:如果想要在 RSS输出中完全不展示,只需要建立一个同名的空文件。

这里提供我修改后的 NeoDB 卡片样式,我做的比较简单,NeoDB 卡片在 RSS 中的展示样式为:

死灵之书 - NeoDB

相关代码:<theme_folder>/layouts/shortcodes/neodb.rss.xml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{{ $dbUrl := .Get 0 }}
{{ $dbApiUrl := "" }}

{{ if ( findRE `^.*neodb\.social\/.*` $dbUrl ) }}
    {{ $dbPath := replaceRE `.*neodb.social\/(.*\/.*)` "$1" $dbUrl }}
    {{ $dbApiUrl = print "https://neodb.social/api/" $dbPath }}
{{ else }}
    {{ $dbApiUrl = print "https://neodb.social/api/catalog/fetch?url=" $dbUrl }}
{{ end }}

{{ with resources.GetRemote $dbApiUrl }}
{{ with .Err }}
{{ warnf "Failed to fetch neodb content, please check API validity: %s" . }}
{{ else }}
    {{ $dbFetch := transform.Unmarshal . }}
    <p><blockquote><a class="link" href="{{ $dbFetch.id }}" >{{ $dbFetch.title }} - NeoDB</a></blockquote></p>
{{ end }}
{{ end }}

短代码篇

首先,对于简单的文本样式,比如居中/居右,修改文字颜色/背景颜色/随地变等功能,可以通过在文中插入html段落来实现,我个人觉得是比新增短代码更轻量级的操作。

如果不知道怎么写,可以使用 在线HTML编辑器 或类似网站/软件来进行可视化编辑,并获取源代码。

NeoDB Card

原有的代码来自 Hi, NeoDB,修改的契机是我更新了 Hugo 版本,在新版本中, getJSON 方法废弃了,因此阅读了原代码并对 shortcode 部分稍作修改。

修改点如下:

  • 使用 resources.GetRemote 代替 getJson
  • 优化了错误处理,避免 NeoDB 不可达(如网络环境问题)导致的本地预览失败,对于此错误:
    • 使用hugo server 命令本地预览时,展示警告信息
    • 使用hugo命令远程构建时,报错并令构建失败
  • 优化评分不足时的展示

样式预览:

评分人数不足
Originally written for the pulp magazines of the 1920s and 1930s, H. P. Lovecraft's astonishing tales blend elements of horror, science fiction, and cosmology that are as powerful today as they were when first published. This tome presents original versions of many of his most harrowing stories, including the complete Cthulhu Mythos cycle, in order of publication. See also: Eldritch Tales .
book
代码部分(点击展开)

最新代码可见:<theme_folder>/layouts/shortcodes/neodb.html

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
{{ $dbUrl := .Get 0 }}
{{ $dbApiUrl := "" }}

{{ if ( findRE `^.*neodb\.social\/.*` $dbUrl ) }}
    {{ $dbPath := replaceRE `.*neodb.social\/(.*\/.*)` "$1" $dbUrl }}
    {{ $dbApiUrl = print "https://neodb.social/api/" $dbPath }}
{{ else }}
    {{ $dbApiUrl = print "https://neodb.social/api/catalog/fetch?url=" $dbUrl }}
{{ end }}

{{ with resources.GetRemote $dbApiUrl }}
    {{ with .Err }}
        {{ if hugo.IsServer }}
            {{ warnf "Failed to fetch neodb content, Please check if NeoDB is reachable: %s" . }}
            <p style="text-align: center;"><small>远程获取内容失败,请检查 NeoDB 是否可达。</small></p>
        {{ else }}
            {{ errorf "Failed to fetch neodb content, Please check if NeoDB is reachable: %s" . }}
        {{ end }}
    {{ else }}
        {{ $dbFetch := transform.Unmarshal . }}
        {{ $itemRating := 0 }}{{ with $dbFetch.rating }}{{ $itemRating = . }}{{ end }}
            <div class="db-card">
                <div class="db-card-subject">
                    <div class="db-card-post"><img loading="lazy" decoding="async" referrerpolicy="no-referrer" src="{{ $dbFetch.cover_image_url }}"></div>
                    <div class="db-card-content">
                        <div class="db-card-title"><a href="{{ $dbUrl }}" class="cute" target="_blank" rel="noreferrer">{{ $dbFetch.title }}</a></div>
                            <div class="rating">
                                {{ if $dbFetch.rating }}
                                <span class="allstardark">
                                    <span class="allstarlight" style="width:{{mul 10 $itemRating }}%"></span>
                                </span>
                                <span class="rating_nums">{{ $itemRating }}</span>
                                {{ else }}
                                    <span class="rating_nums" style="color:#737373;">评分人数不足</span>
                                {{ end }}
                            </div>
                        <div class="db-card-abstract">{{ $dbFetch.brief }}</div>
                    </div>
                    <div class="db-card-cate">{{ $dbFetch.category }}</div>
                </div>
            </div>
    {{ end }}
{{ end }}

<style>
    .db-card {
        margin: 2rem 3rem;
        background: var(--body-background);
        border-radius: 8px;
        box-shadow: var(--shadow-l1);
    }

    .db-card-subject {
        display: flex;
        align-items: flex-start;
        line-height: 1.6;
        padding: 12px;
        position: relative
    }

    .db-card-content {
        flex: 1 1 auto
    }

    .db-card-post {
        width: 96px;
        margin-right: 15px;
        display: flex;
        flex: 0 0 auto
    }

    .db-card-title {
        margin-bottom: 5px;
        font-size: 18px
    }

    .db-card-title a {
        text-decoration: none!important
    }

    .db-card-abstract,
    .db-card-comment {
        font-size: 14px;
        overflow: auto;
        max-height: 7rem
    }

    .db-card-cate {
        position: absolute;
        top: 0;
        right: 0;
        background: #f99b0170;
        padding: 1px 8px;
        font-size: small;
        font-style: italic;
        border-radius: 0 8px 0 8px;
        text-transform: capitalize
    }

    .db-card-post img {
        width: 96px!important;
        height: 128px!important;
        border-radius: 4px;
        -o-object-fit: cover;
        object-fit: cover
    }

    .rating {
        margin: 0 0 5px;
        font-size: 13px;
        line-height: 1;
        display: flex;
        align-items: center
    }

    .rating .allstardark {
        position: relative;
        color: #f99b01;
        height: 16px;
        width: 80px;
        background-size: auto 100%;
        margin-right: 8px;
        background-repeat: repeat;
        background-image: url(data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMzIiIGhlaWdodD0iMzIiPjxwYXRoIGQ9Ik05MDguMSAzNTMuMWwtMjUzLjktMzYuOUw1NDAuNyA4Ni4xYy0zLjEtNi4zLTguMi0xMS40LTE0LjUtMTQuNS0xNS44LTcuOC0zNS0xLjMtNDIuOSAxNC41TDM2OS44IDMxNi4ybC0yNTMuOSAzNi45Yy03IDEtMTMuNCA0LjMtMTguMyA5LjMtMTIuMyAxMi43LTEyLjEgMzIuOS42IDQ1LjNsMTgzLjcgMTc5LjEtNDMuNCAyNTIuOWMtMS4yIDYuOS0uMSAxNC4xIDMuMiAyMC4zIDguMiAxNS42IDI3LjYgMjEuNyA0My4yIDEzLjRMNTEyIDc1NGwyMjcuMSAxMTkuNGM2LjIgMy4zIDEzLjQgNC40IDIwLjMgMy4yIDE3LjQtMyAyOS4xLTE5LjUgMjYuMS0zNi45bC00My40LTI1Mi45IDE4My43LTE3OS4xYzUtNC45IDguMy0xMS4zIDkuMy0xOC4zIDIuNy0xNy41LTkuNS0zMy43LTI3LTM2LjN6TTY2NC44IDU2MS42bDM2LjEgMjEwLjNMNTEyIDY3Mi43IDMyMy4xIDc3MmwzNi4xLTIxMC4zLTE1Mi44LTE0OUw0MTcuNiAzODIgNTEyIDE5MC43IDYwNi40IDM4MmwyMTEuMiAzMC43LTE1Mi44IDE0OC45eiIgZmlsbD0iI2Y5OWIwMSIvPjwvc3ZnPg==)
    }

    .rating .allstarlight {
        position: absolute;
        left: 0;
        color: #f99b01;
        height: 16px;
        overflow: hidden;
        background-size: auto 100%;
        background-repeat: repeat;
        background-image: url(data:image/svg+xml;base64,PHN2ZyBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMzIiIGhlaWdodD0iMzIiPjxwYXRoIGQ9Ik05MDguMSAzNTMuMWwtMjUzLjktMzYuOUw1NDAuNyA4Ni4xYy0zLjEtNi4zLTguMi0xMS40LTE0LjUtMTQuNS0xNS44LTcuOC0zNS0xLjMtNDIuOSAxNC41TDM2OS44IDMxNi4ybC0yNTMuOSAzNi45Yy03IDEtMTMuNCA0LjMtMTguMyA5LjMtMTIuMyAxMi43LTEyLjEgMzIuOS42IDQ1LjNsMTgzLjcgMTc5LjEtNDMuNCAyNTIuOWMtMS4yIDYuOS0uMSAxNC4xIDMuMiAyMC4zIDguMiAxNS42IDI3LjYgMjEuNyA0My4yIDEzLjRMNTEyIDc1NGwyMjcuMSAxMTkuNGM2LjIgMy4zIDEzLjQgNC40IDIwLjMgMy4yIDE3LjQtMyAyOS4xLTE5LjUgMjYuMS0zNi45bC00My40LTI1Mi45IDE4My43LTE3OS4xYzUtNC45IDguMy0xMS4zIDkuMy0xOC4zIDIuNy0xNy41LTkuNS0zMy43LTI3LTM2LjN6IiBmaWxsPSIjZjk5YjAxIi8+PC9zdmc+)
    }

    @media(max-width: 550px) {
        .db-card {
            margin: .8rem 1rem
        }
        .db-card-comment {
            display: none
        }
    }
</style>

展柜

Library 游戏部分的样式的灵感来源是这篇文章:Hugo 「近期」短代码,不过我对同步豆瓣/NeoDB 的记录兴趣不大,而是想要自己展示一些东西,所以代码是自己重写的,比较简洁,数据文件也使用了 json 格式,而不是原文的 csv

后续有更多内容后,考虑增加轮播的灯箱样式,目前暂时用不上。

另外如果想要卡片式的展柜,也可以考虑:Hugo 创建「好物」页面这篇文章中的实现。

使用说明(点击展开)
  1. 新建 <theme_folder>/layouts/shortcodes/work-list.html
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
{{ $file := .Get 0 }}  
{{ $num := 4 }}  
{{ if .Get 1 }}   
    {{ $num = .Get 1 }}  
{{ end }}  
{{ $width := div 90 $num }}  
{{ $items := dict }}  
{{ with resources.Get $file }}  
    {{ $items = . | transform.Unmarshal }}  
{{ else }}  
    {{ errorf "Unable to get resource %q" $file }}  
{{ end }}  
  
<div class="work-list">  
    {{range $item := $items}}  
        <a href="{{ $item.url }}">  
            <img class="work-list-item" src="{{ $item.image }}" loading="lazy" title="{{ $item.title }}" width="{{ $width }}%">  
        </a>    
    {{end}}  
</div>
  1. 创建数据文件:

需要注意的是,hugo 数据的根目录在升级之后有变化:

  • 低版本:<blog>/static/data/example.json
  • 高版本:<blog>/assert/data/example.json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[  
    {  
      "title": "血源诅咒",  
      "url": "https://www.igdb.com/games/bloodborne",  
      "image": "https://images.igdb.com/igdb/image/upload/t_cover_big/co1rba.png"  
    },  
    {  
        "title": "塞尔达传说:王国之泪",  
        "url": "https://www.igdb.com/games/the-legend-of-zelda-tears-of-the-kingdom",  
        "image": "https://images.igdb.com/igdb/image/upload/t_cover_big/co5vmg.png"  
    }
]

使用示例:

1
2
{{< work-list "data/example.json">}}     // 默认为每行4个图片
{{< work-list "data/example.json" 6 >}}  // 指定每行6个图片

样式预览:

评论区篇

对这个评论区实在有点相看两厌,似乎之前配置的邮件通知也坏掉了。不过终于成功把它改的不那么丑了一些。

文档也很难找,最后通过读代码才找到修改样式相关的CSS变量,对此非常不满。

css 文件位置在:themes/hugo-theme-stack/layouts/partials/comments/provider/waline.html,我改了三个地方,更多的可以看上面的文档,或者通过右键-检查来找到对应的变量。

1
2
3
--waline-border: 0px;                             // 隐藏丑陋的边框
--waline-border-color: var(--waline-bgcolor);     // 隐藏丑陋的粗虚线
--waline-theme-color: var(--accent-color-darker); // 更换绿绿的颜色

为了降低我对这个评论区的不满,特此宣布:我在评论区表情包中增加了小豆泥。

Licensed under CC BY-NC-ND 4.0