Hugo / LoveIt 里,为什么 title 里的空格总是不生效
一次从 shortcode 怀疑到字符层落点的排查记录

有些问题并不大,却会把人卡很久。这次卡住我的,不是功能失效,不是编译报错,而是一个看上去极小的细节:我在 Hugo + LoveIt 的 admonition 标题里敲下去的那些空格,页面上就是不肯按预期留下来。
问题是怎么出现的
我想在 admonition 的标题里,把一句话和"《内容已折叠,点击展开》“稍微拉开一点距离,于是最直接的做法,就是在中间敲一串空格。写的时候看着没问题,源码里也确实有空格,但最终渲染出来,它们几乎像没存在过一样,只剩下一点很短的间隔。
当时使用的写法大概是这样:
{{< admonition type=success title="希望我写的每一个字,成为我自己和某个人活下去、拼下去的力量。 《内容已折叠,点击展开》" open=false >}}
这里是正文内容
{{< /admonition >}}上面代码里的
{没这字{</* 代码 */>}}写法是 Hugo 的代码展示专用语法,展示时不会被解析执行。正常使用时写成{没这字{< admonition 代码 >}}即可。
最开始我怀疑了什么
最先怀疑的当然是 shortcode 本身。因为 admonition 不是 Markdown 原生语法,而是主题层的扩展,我第一反应是:是不是参数传递时把空格裁掉了,或者 partial 渲染 title 的时候发生了转义、压缩,导致中间那段空白在模板层就已经丢了。
我翻出了 shortcode 文件,看到它大致是这样的:
{{- $inner := .Inner | .Page.RenderString -}}
{{- $options := .Inner | .Page.RenderString | dict "Inner" -}}
{{- if .IsNamedParams -}}
{{- $options = dict "Type" (.Get "type") "Title" (.Get "title") "Open" (.Get "open") | merge $options -}}
{{- else -}}
{{- $options = dict "Type" (.Get 0) "Title" (.Get 1) "Open" (.Get 2) | merge $options -}}
{{- end -}}
{{- partial "plugin/admonition.html" $options -}}这一步看上去很可疑,Title 作为字符串传进去,我顺着往下怀疑:是不是 plugin/admonition.html 在输出 {{ .Title }} 时,没有保留间距,又让浏览器把普通空格折叠掉了。
哪些表象其实是误导
真正误导我的,不是代码,而是"看起来像模板问题"的那个外观。因为当你看到 title 在 shortcode 里传来传去,再想到 safeHTML、转义、partial,这条排查路径天然就显得很像正确答案。
但后来我意识到,模板层确实可能影响 这类 HTML 实体是否生效,却并不能解释一件更基础的事:我输入的那一串,本来就只是普通空格。而普通空格在 HTML 里,本来就会被浏览器折叠成一个,这不是 LoveIt 的特殊行为,也不是 Hugo 在偷偷做坏事。
问题真正落点,反而比 shortcode 和 partial 都更浅:在字符本身。
我看到了什么关键证据
真正让我把问题看清的,是一个非常朴素的对比。同样是"空白”,普通空格怎么敲都不稳定;而当我换成全角空格以后,间距立刻就留下来了,而且不是偶然成功,是每次都成功。
最后采用的写法,只是把中间那段普通空格,替换成了几个全角空格:
{{< admonition
type=success
title="希望我写的每一个字,成为我自己和某个人活下去、拼下去的力量。 《内容已折叠,点击展开》"
open=false
>}}
「随笔・思绪流淌 自救重生」系列第71篇 · 待续
{{< /admonition >}}这里的 (光标放上去会发现比普通空格宽)不是视觉上的普通空格,而是 Unicode 里的全角空格,字符码位是 U+3000。对浏览器来说,它不是一串可以随手折叠掉的空白分隔符,而是一个真正会参与排版的字符,放几个就显示几个宽度。
真正根因是什么
真正根因可以分成两层看。
第一层,是 HTML 的默认规则:连续的普通空格会被折叠,所以你在源码里看到十个空格,不等于浏览器会渲染出十个空格宽度。这意味着"源码里明明有空格"这件事,本身不能作为空格应该显示的证据。
第二层,是我最开始把"空格失效"理解成了"shortcode 渲染异常"。其实 shortcode 没坏,partial 也未必有错,至少这一次,它们不是最先该动手的地方。真正失效的,不是模板机制,而是我所选用的那个字符,天生就不适合拿来做这类可见间距。
最终修复方案
我最后没有去改 shortcode,也没有改 plugin/admonition.html,直接使用了全角空格。
我的具体操作方式:
- 在 Windows 右下角找到微软拼音输入法图标,鼠标右键点击
- 在弹出菜单里选择全角,切换到全角状态
- 在 title 里需要空格的位置,直接按键盘空格键,输入的就是全角空格

有一个容易忽略的细节:后续如果需要增删这段空白,也要先切回全角状态再操作,否则补进去的新空格又会变成普通空格,视觉上很容易前后不一致。
这次最值得记住的经验
这次最值得记住的,不是"全角空格很好用",而是另一句话:先确认问题发生在哪一层,再决定要不要往更深的地方挖。
很多技术问题一开始都像模板问题、框架问题、渲染问题,但如果最底层的输入对象本身就不满足规则,那你越往深处查,越容易把自己带偏。
这一次,真正起作用的不是更复杂的修复,而是更准确的定位。当我承认"这里需要的不是普通空格",后面的路就突然安静了下来。
梦行志
