【前情提要】 文章写好了,标签配好了,列表页也有了。老周说:“明天让博客有点‘人样’,加图片、代码块、短代码。”小林打开 PaperMod 文档,准备大干一场。但他不知道,Hugo 的 shortcode、代码高亮、TOC,每一个都有细节。

一、老周的审美暴击

早上到工位,小林得意地打开博客,给老周展示昨天写的第一篇文章。

老周看了一眼,沉默了三秒。

“你知道你这篇文章像什么吗?”

“像什么?”

“像上世纪九十年代的 BBS。”老周说,“纯文字,没图,代码块连高亮都没有。你管这叫技术博客?”

小林委屈:“我大学写实验报告就这样……”

“大学是大学,公司是公司。”老周拉过椅子,“今天教你三件事:图片、代码高亮、TOC。搞完你这篇博客至少能看。”

二、图片:路径、格式、大小

“先说你昨天那张 Hugo Logo。”老周说,“你放哪了?”

“static/images/ 啊。”

“对。”老周说,“但你有没有发现一个问题:你每次换主题,图片路径不变,但显示效果可能变。为什么?”

“因为……主题的 CSS 不一样?”

“对。图片的‘大小’是主题的 CSS 控制的,你写 ![]() 只是插入图片,不控制大小。所以,图片本身的尺寸不要太大,不然主题再好看也救不了你。”

老周让小林检查图片大小:

ls -lh static/images/

输出:

-rw-r--r-- 1 devops devops 2.3M Sep 13 10:00 hugo-logo.png

“2.3M?”老周说,“一张 Logo 2.3M,你首页加载要等三秒。”

“那怎么办?”

“压缩。”老周说,“图片宽度 800 到 1200 像素就够了,再大没意义。你用 imagemagick 压缩。”

sudo apt install -y imagemagick
convert hugo-logo.png -resize 800x hugo-logo-small.png

“convert 是 ImageMagick 的命令,-resize 800x 表示宽度缩到 800,高度等比例。”老周说,“压缩完看看。”

ls -lh static/images/

输出:

-rw-r--r-- 1 devops devops  80K Sep 13 10:05 hugo-logo-small.png
-rw-r--r-- 1 devops devops 2.3M Sep 13 10:00 hugo-logo.png

“2.3M 变 80K。”小林惊了,“这差距也太大了吧。”

“所以,以后写文章,图片先压缩再上传。 你可以用 TinyPNG 在线压,也可以用 ImageMagick 本地压。反正别把相机原图直接丢进去。”

小林记下:

  • 图片宽度 800~1200px 够用
  • 用 convert -resize 800x 压缩
  • 图片放 static/images/,引用用 /images/xxx.png

“还有一个坑。”老周说,“图片格式。 PNG 适合截图和透明背景,JPG 适合照片。你截图用 PNG,照片用 JPG。别搞反,不然要么模糊,要么巨大。”

“那 WebP 呢?”

“WebP 更小,但不是所有浏览器都支持。你现在别折腾,PNG 和 JPG 够用了。”

三、图片的进阶用法:Figure Shortcode

“普通的 ![]() 够用,但 PaperMod 支持一个更高级的写法,叫 shortcode。”老周说。

“shortcode?”

“你可以理解成 Hugo 的‘扩展标签’。Hugo 内置了一些 shortcode,主题也可以定义自己的 shortcode。PaperMod 内置了 figure。”

小林打开文章,找到图片那行,改成:

{{< figure src="/images/hugo-logo-small.png" title="Hugo Logo" caption="Hugo 的官方 Logo" >}}

保存,刷新。

图片出来了,下面多了一行小字:“Hugo 的官方 Logo”。

“看到了吧?”老周说,“figure 会自动加 figure 和 figcaption 标签,图片下面有说明文字。而且 PaperMod 给它加了样式,比裸 ![]() 好看。”

“那 figure 还有别的参数吗?”

“有。”老周说,“常用的几个:”

  • src:图片路径
  • title:图片标题
  • caption:图片说明
  • alt:替代文本,SEO 和无障碍访问用
  • link:点击图片跳转的链接
  • width:图片宽度
  • height:图片高度
  • class:自定义 CSS 类

“你一般写 src、alt、caption 就够了。”老周说,“比如:”

{{< figure src="/images/hugo-logo-small.png" alt="Hugo Logo" caption="Hugo 是一个静态网站生成器" >}}

小林改了一下,刷新。

“那 {{< >}} 和 {{% %}} 有啥区别?”小林问。

“好问题。”老周说,“{{< >}} 表示 shortcode 的内容不经过 Markdown 渲染。{{% %}} 表示内容经过 Markdown 渲染。你以后写 shortcode,如果里面是纯 HTML,用 {{< >}};如果是 Markdown,用 {{% %}}。”

“那 figure 用哪个?”

“figure 用的是 {{< >}},因为它的参数是 Hugo 处理的,不需要 Markdown 再渲染。”

小林记下:{{< >}} 不渲染 Markdown,{{% %}} 渲染 Markdown。

四、代码块:从“能看”到“好看”

“图片搞定了,说代码块。”老周说,“你昨天的代码块,连高亮都没有。”

“有啊,我写了 ```bash。”

“写了是写了,但 PaperMod 默认的代码高亮是什么?”

小林刷新文章,看了一眼代码块。白色背景,黑色文字,确实没有语法高亮。

“因为 PaperMod 默认用的是 Hugo 内置的 Chroma 高亮,但你需要在 hugo.toml 里配置。”老周说。

在 hugo.toml 里加:

[markup]
    [markup.highlight]
        noClasses = false
        lineNos = true
        lineNumbersInTable = true
        style = "monokai"

“一个一个说。”

  • noClasses = false:用 CSS 类而不是内联样式。这样主题可以覆盖。
  • lineNos = true:显示行号。
  • lineNumbersInTable = true:行号和代码用表格分开,复制代码不会把行号复制进去。
  • style = "monokai":高亮配色方案。常用的有 monokai、dracula、github、solarized-dark。

小林保存,刷新。

代码块有了行号,有了颜色。

“但我看行号挡住了复制按钮。”小林说。

“正常,PaperMod 的复制按钮在代码块右上角,行号在左边,不冲突。”老周说,“你试试复制。”

小林点了一下复制按钮,代码复制成功,没有行号。

“好用了。”小林说。

“再教你一个技巧。”老周说,“代码块可以指定高亮行。”

```bash {hl_lines=[2,4]}
hugo version
hugo server
ls -la
hugo new site myblog
```

“这样第 2 行和第 4 行会高亮,读者一眼看到重点。”

小林试了一下,果然。

“还有一个:行号从第几行开始。”

```bash {linenostart=10}
hugo version
hugo server
```

“这样行号从 10 开始,适合展示长文件的一部分。”

“记下:”

  • {hl_lines=[2,4]}:高亮第 2、4 行
  • {linenostart=10}:行号从 10 开始
  • {linenos=false}:这一块不显示行号

五、TOC:文章的“导航”

“第三件事,TOC。”老周说,“你文章长了,读者想跳到某一节,得有个目录。”

“PaperMod 不是有 ShowToc = true 吗?”

“对,你在 hugo.toml 里配了 ShowToc = true。但文章级别也可以单独控制。”

小林打开文章 front matter,加:

ShowToc: true
TocOpen: false

“ShowToc: true 显示目录,TocOpen: false 默认折叠。”老周说,“你保存,刷新。”

文章右侧出现了目录,列出了所有二级标题。

“点了能跳吗?”

“能。”老周说,“PaperMod 的 TOC 是锚点跳转,点一下滚到对应位置。”

“那目录的标题层级怎么控制?”

“默认是二级和三级标题。你可以在 hugo.toml 里配:”

[markup]
    [markup.tableOfContents]
        startLevel = 2
        endLevel = 4
        ordered = false

“startLevel = 2 从二级标题开始,endLevel = 4 到四级标题结束,ordered = false 不用数字编号。”

“那如果我不想显示某一篇的目录?”

“在 front matter 里写 ShowToc: false,覆盖全局配置。”

小林记下:

  • 全局:hugo.toml 中 ShowToc = true
  • 单篇:front matter 中 ShowToc: false 覆盖
  • TOC 层级:startLevel 和 endLevel

六、短代码:Hugo 的“扩展标签”

“刚才讲了 figure,那是 Hugo 内置的 shortcode。”老周说,“Hugo 还有几个内置 shortcode,你以后会用。”

ref 和 relref

“你想在文章里链接到另一篇文章,不要写死 URL,用 ref:”

[我的第一篇文章]({{< ref "posts/my-first-post.md" >}})

“ref 会生成相对于 baseURL 的链接,relref 生成相对链接。你以后重构站点,URL 变了,ref 自动更新,写死 URL 就得手动改。”

youtube

“你想嵌入 YouTube 视频:”

{{< youtube dQw4w9WgXcQ >}}

“它会生成一个 iframe。”

highlight

“你想在 shortcode 里写代码:”

{{< highlight bash >}}
hugo version
hugo server
{{< /highlight >}}

“效果和 ```bash 一样,但 shortcode 更灵活。”

details

“你想折叠一段内容:”

{{< details "点击展开" >}}
这里是隐藏的内容。
{{< /details >}}

“读者点一下才展开。你以后写‘排错挑战’的答案,可以用这个折叠。”

小林眼睛一亮:“这个好,我练习答案就可以折叠。”

“对。”老周说,“PaperMod 支持 details,你试试。”

小林在文章末尾加了:

{{< details "参考答案" >}}
这是答案内容。
{{< /details >}}

刷新,出现一个“参考答案”的折叠块,点一下展开。

“成了。”小林满意地说。

七、排错挑战:图片 404

下午,小林想把一张新图片加到文章里。他下载了一张图,放到 static/images/,然后在文章里写:

![架构图](/images/Architecture-Diagram.png)

保存,刷新。

图片挂了。

他检查文件:

ls -l static/images/

输出:

-rw-r--r-- 1 devops devops 80K Sep 13 10:05 hugo-logo-small.png
-rw-r--r-- 1 devops devops 120K Sep 13 14:00 architecture-diagram.png

“文件名是 architecture-diagram.png,全小写。”小林说,“我引用写的是 Architecture-Diagram.png,大写……”

他改回来:

![架构图](/images/architecture-diagram.png)

刷新,图片出来了。

“又是大小写。”小林叹气。

“我昨天就说了。”老周路过,“Linux 大小写敏感,Mac 不敏感。你在 Mac 上写博客,图片能显示,传到 Ubuntu 就挂。 所以文件名全小写,引用也全小写。永远别用大写。”

“那如果我的图片名本来就有大写呢?”

“改。”老周说,“mv 重命名,或者下载的时候就改成小写。别偷懒。”

排错思路总结:

  1. 图片 404,先检查文件是否在 static/images/ 下。
  2. 检查文件名和引用路径是否完全一致(大小写敏感)。
  3. 检查引用路径是否以 / 开头(绝对路径)。
  4. 检查 hugo server 是否重新构建(改了 static/ 不需要重启,但 hugo.toml 改了要重启)。
  5. 浏览器强制刷新(Ctrl + Shift + R),排除缓存。

“记住,”老周说,“Hugo 的静态文件,路径就是 URL。 static/images/a.png 对应 /images/a.png。static/images/A.png 对应 /images/A.png。Linux 下这俩是两个文件,Mac 下是一个。所以,全小写,保平安。”

八、练习

  1. 练习一:压缩一张图片到宽度 800px,放到 static/images/ 下。用 figure shortcode 插入文章,设置 alt 和 caption。确认页面显示图片和说明文字。
  2. 练习二:在 hugo.toml 中配置代码高亮 style = "monokai"、lineNos = true、lineNumbersInTable = true。在文章中写一个代码块,用 {hl_lines=[2]} 高亮第 2 行,确认行号和高亮生效。
  3. 练习三:在文章 front matter 中设置 ShowToc: true、TocOpen: false。确认文章右侧显示目录,点击目录项能跳转。然后用 details shortcode 包裹一段答案,确认可折叠。

参考答案

练习一:

sudo apt install -y imagemagick
convert 原图.png -resize 800x static/images/new-image.png

文章中添加:

{{< figure src="/images/new-image.png" alt="示例图片" caption="这是示例图片的说明" >}}

练习二:

hugo.toml:

[markup]
    [markup.highlight]
        noClasses = false
        lineNos = true
        lineNumbersInTable = true
        style = "monokai"

文章代码块:

```bash {hl_lines=[2]}
hugo version
hugo server
```

练习三:

front matter:

ShowToc: true
TocOpen: false

正文中加:

{{< details "答案" >}}
这是折叠的答案内容。
{{< /details >}}

九、下集预告

图片、代码块、TOC 都搞定了,博客终于“有个人样”了。老周说:“明天让 Nginx 接管,把 hugo server 换成正经的 80 端口。”小林打开 Nginx 文档,准备配置。但他不知道,try_files、root 路径、权限,每一个都可能让他折腾一下午。

下一集:《让 Nginx 接管:从 localhost:1313 到正经端口》