Hugo模板使用介绍,hugo的主题制作最主要的部分就是模板,模板的基础部分包含代码块,注释,变量,包含的方式等等.
1. 基础部分
参考: Introduction
1.1. 访问预定义变量
1
2
|
{{ .Title }}
{{ $address }}
|
1.2. 访问方法
用空格分开参数
1
2
3
|
{{ FUNCTION ARG1 ARG2 .. }}
示例
{{ add 1 2 }}
|
1.3. 访问对象的方法或者属性
用.
来进行访问
1.4. 分组代码块
用圆括号来进行分组
1
|
{{ if or (isset .Params "alt") (isset .Params "caption") }} Caption {{ end }}
|
1.5. 多行代码
用大括号包裹的代码块可以写成多行
1
2
3
4
|
{{ if or
(isset .Params "alt")
(isset .Params "caption")
}}
|
1.6. 多行字符串
1
2
|
{{ $msg := `Line one.
Line two.` }}
|
2. Variables
.
代表当前scope
用$
来定义个访问局部变量
1
2
|
{{ $address := "123 Main St." }}
{{ $address }}
|
3. Functions
template functions有预定义的大量模板变量
方法调用
1
2
3
4
|
{{ add 1 2 }}
<!-- prints 3 -->
{{ lt 1 2 }}
<!-- prints true (i.e., since 1 is less than 2) -->
|
4. Include
5. Partial
包含模板
1
|
{{ partial "header.html" . }}
|
6. Template
在老版本是用来包含partial模板的,现在只有在内部模板包含的时候才会使用
1
2
|
包含内部的opengraph.html
{{ template "_internal/opengraph.html" . }}
|
7. Logic
7.1. Iteration迭代
需要注意的是,有些语法会改变scope,比如range
语法块,内部的scope就外部的不一样,.
的意义就改变了
用.
来直接访问迭代的变量
1
2
3
|
{{ range $array }}
{{ . }} <!-- The . represents an element in $array -->
{{ end }}
|
定一个局部变量
1
2
3
|
{{ range $elem_val := $array }}
{{ $elem_val }}
{{ end }}
|
加上索引
1
2
3
|
{{ range $elem_index, $elem_val := $array }}
{{ $elem_index }} -- {{ $elem_val }}
{{ end }}
|
map迭代
1
2
3
|
{{ range $elem_key, $elem_val := $map }}
{{ $elem_key }} -- {{ $elem_val }}
{{ end }}
|
range还有个特殊的使用方式,可以判断可迭代的变量是否为空
1
2
3
4
5
|
{{ range $array }}
{{ . }}
{{else}}
<!-- This is only evaluated if $array is empty -->
{{ end }}
|
7.2. Conditionals条件判断
关键字
在hugo的模板语言里,下面得都为false
- false
- 0
- 或者一个空的array,map,slice或者string
with和range一样,会重写context,那么.
的意义就变了,if不会重写context
1
2
3
|
{{ if isset .Params "title" }}
<h4>{{ index .Params "title" }}</h4>
{{ end }}
|
and
和or
1
|
{{ if (and (or (isset .Params "title") (isset .Params "caption")) (isset .Params "attr")) }}
|
8. Pipes管道
用过django的话,管道应该不会陌生,对变量进行处理,stream模式的方法调用,其实就是简化的函数调用
1
2
3
|
{{ shuffle (seq 1 5) }}
可以写成
{{ (seq 1 5) | shuffle }}
|
isset的使用示例
1
2
3
|
{{ if isset .Params "caption" | or isset .Params "title" | or isset .Params "attr" }}
Stuff Here
{{ end }}
|
这样看起来就清晰很多,比下面这种写很多括号的方式要好理解多了
1
2
3
|
{{ if or (or (isset .Params "title") (isset .Params "caption")) (isset .Params "attr") }}
Stuff Here
{{ end }}
|
hugo的模板默认会转义html语言,safeHTML可以保持原样输出
1
2
3
|
{{ "<!--[if lt IE 9]>" | safeHTML }}
<script src="html5shiv.js"></script>
{{ "<![endif]-->" | safeHTML }}
|
9. Context上下文
.
代表当前context
- 在顶部模板里,
.
就是全局变量数据集
- 在迭代器里
.
是当前的item
如果想使用外部context的数据,可以这样
1
2
3
4
5
6
7
8
9
|
{{ $title := .Site.Title }}
<ul>
{{ range .Params.tags }}
<li>
<a href="/tags/{{ . | urlize }}">{{ . }}</a>
- {{ $title }}
</li>
{{ end }}
</ul>
|
或者用$符号
1
2
3
4
5
6
7
8
|
<ul>
{{ range .Params.tags }}
<li>
<a href="/tags/{{ . | urlize }}">{{ . }}</a>
- {{ $.Site.Title }}
</li>
{{ end }}
</ul>
|
10. 去除空白字符串
用-
1
2
3
4
5
6
7
|
<div>
{{ .Title }}
</div>
会输出
<div>
Hello, World!
</div>
|
1
2
3
4
5
|
<div>
{{- .Title -}}
</div>
会输出
<div>Hello, World!</div>
|
这几种都会被自动处理
- space
- horizontal tab
- carriage return
- newline
{{/*
and */}}
1
|
Bonsoir, {{/* {{ add 0 + 2 }} */}}Eliott.
|
html注释
1
|
{{ printf "<!-- Our website is named: %s -->" .Site.Title | safeHTML }}
|
12. Hugo Parameters
12.1. Page Parameter
1
2
3
4
5
6
|
---
title: Roadmap
lastmod: 2017-03-05
date: 2013-11-18
notoc: true
---
|
如果要访问notoc
1
2
3
4
5
6
7
8
9
10
11
|
{{ if not .Params.notoc }}
<aside>
<header>
<a href="#{{.Title | urlize}}">
<h3>{{.Title}}</h3>
</a>
</header>
{{.TableOfContents}}
</aside>
<a href="#" id="toc-toggle"></a>
{{ end }}
|
访问站点配置里的变量
1
2
3
4
|
[params]
copyrighthtml = "Copyright © 2017 John Doe. All Rights Reserved."
sidebarrecentlimit = 5
twitteruser = "spf13"
|
1
2
3
4
5
|
{{ if .Site.Params.copyrighthtml }}
<footer>
<div class="text-center">{{.Site.Params.CopyrightHTML | safeHTML}}</div>
</footer>
{{ end }}
|
使用with
来直接访问parameter
1
2
3
4
5
6
|
{{ with .Site.Params.twitteruser }}
<div>
<a href="https://twitter.com/{{.}}" rel="author">
<img src="/images/twitter.png" width="48" height="48" title="Twitter: {{.}}" alt="Twitter"></a>
</div>
{{ end }}
|
示例:配置显示数量
1
2
3
4
5
6
7
8
|
<nav>
<h1>Recent Posts</h1>
<ul>
{{- range first .Site.Params.SidebarRecentLimit .Site.Pages -}}
<li><a href="{{.RelPermalink}}">{{.Title}}</a></li>
{{- end -}}
</ul>
</nav>
|
示例:显示某个section下的近期文章
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<h4>Upcoming Events</h4>
<ul class="upcoming-events">
{{ range where .Pages.ByDate "Section" "events" }}
{{ if ge .Date.Unix now.Unix }}
<li>
<!-- add span for event type -->
<span>{{ .Type | title }} —</span>
{{ .Title }} on
<!-- add span for event date -->
<span>{{ .Date.Format "2 January at 3:04pm" }}</span>
at {{ .Params.place }}
</li>
{{ end }}
{{ end }}
</ul>
|
未整理的
给文章标题自动增加锚点
方法一 利用hugo的过滤器来进行正则表达式替换
single.html
1
|
{{ partial "main/headline-hash.html" .Content }}
|
headline-hash.html
1
|
{{ . | replaceRE "(<h[2-9] id=\"([^\"]+)\".+)(</h[2-9]+>)" `${1}<a href="#${2}" class="anchor" aria-hidden="true">#</a> ${3}` | safeHTML }}
|
然后给anchor一些样式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
.anchor {
visibility: hidden;
padding-left: 0.5rem;
}
h1,h2,h3,h4,h5,h6 {
a.anchor {
opacity: 0;
font-size: 0.75em;
vertical-align: middle;
text-decoration: none;
}
&:hover a.anchor,
a.anchor:focus {
opacity: initial;
}
}
|
访问配置文件参数
1
2
3
4
5
|
{{- if .Site.Params.logoTitle -}}
{{ .Site.Params.logoTitle }}
{{- else -}}
{{ .Site.Title }}
{{- end -}}
|
手动summary
在文中添加`
之前的部分会自动算成
Summary`,参考:Content Summaries | Hugo (gohugo.io)
之前发现summary的字数不对,是因为配置里没有添加hasCJKLanguage
, 参考: Configure Hugo | Hugo (gohugo.io)
关于语法高亮
Hugo里语法高亮默认用的是Chroma(一个基于Pygments的Go语言的实现,所以是完全兼容pygments的), 可以使用配置的方式来更换主题,也可以自定义
过去的配置是这样的,在一些老文档里能找到Hugo | Syntax Highlighting (gohugobrasil.netlify.app)
1
2
3
4
|
pygmentsOptions = "linenos=table"
pygmentsCodefences = true
pygmentsUseClasses = true
pygmentsCodefencesGuessSyntax = true
|
现在的配置是这样的,在markup的配置下面的highlight进行配置
如果配置了pygmentsUseClasses=true
那么就代表你需要用css来控制语法高亮,否则你可以通过配置的方式来设置高亮的显示样式
1
2
3
4
5
6
7
8
9
10
11
12
13
|
[markup]
[markup.highlight]
anchorLineNos = false # 行号增加锚点,如果是true行号就会被a标签包裹
codeFences = true # 代码围栏
guessSyntax = false # 语法猜测
hl_Lines = "" # 高亮的行
lineAnchors = "" # 如果不为空, 那么就会给行号锚点增加一个id和名字,方便进行链接
lineNoStart = 1 # 行的起始编号
lineNos = false # 不显示行号
lineNumbersInTable = true # 把行号放在在table标签里
noClasses = true # 不适用class来显示样式
style = "monokai" # 如果
tabWidth = 4
|
这里有些配置可以到pygments的文档里查找Available formatters — Pygments
参考:
markdown解析
goldmark
是go写的markdown解析器,hugo的默认解析器
分页paginate
Pagination | Hugo (gohugo.io)
在homepage,section,taxonomy这3类页面模板上是支持分页的,而且支持简单的where
和order
,类似sql
List页面都有一个默认的变量.Paginator
配置
关于如何实现专题页面
效果像一个文档专题,有自己的导航,甚至样式,要求
-
导航不是来之配置而是来自相关的文章
-
导航可以多级分布,其实有两级就够了
-
专题可以任意添加,可以用一套模板就够了
-
专题的文章无需特殊,来自post即可
当文章多了(或者说文章是比较有结构性的,可以整理成系列), 比如我想将git, css, 或者python, 单独建专题,
而不是简单的流式blog,希望有下面这种专题形式

继续研究toha
的模板和官方文档
这是包含sidebar的页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
{{ define "sidebar" }}
<section class="sidebar-section" id="sidebar-section">
<div class="sidebar-holder">
<div class="sidebar" id="sidebar">
<form class="mx-auto" method="get" action="{{ "search" | relLangURL }}">
<input type="text" name="keyword" value="" placeholder="Search" data-search="" id="search-box" />
</form>
<div class="sidebar-tree">
<ul class="tree" id="tree">
<li id="list-heading"><a href="{{ "/posts" | relLangURL }}" data-filter="all">{{ i18n "posts" }}</a></li>
<div class="subtree">
{{ partial "navigators/sidebar.html" (dict "menuName" "sidebar" "menuItems" site.Menus.sidebar "ctx" .) }}
</div>
</ul>
</div>
</div>
</div>
</section>
{{ end }}
|
这是sidebar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
{{ range .menuItems }}
{{ $class:= "" }}
{{ $icon:= "fa-plus-circle" }}
<!-- If the current menu is the selected menu or it contain the selected menu, set expand icon and set "active" class -->
{{ if or ($.ctx.HasMenuCurrent $.menuName .) ($.ctx.IsMenuCurrent $.menuName .)}}
{{ $icon = "fa-minus-circle"}}
{{ $class = "active" }}
{{end}}
{{ if .HasChildren }}
<!-- Add current entry -->
<li>
<i class="fas {{ $icon }}"></i><a class="{{$class}}" href="{{ .URL }}">{{.Name}}</a>
<!-- Add sub-tree -->
<ul class="{{ $class }}">
{{ partial "navigators/sidebar.html" (dict "menuName" $.menuName "menuItems" .Children "ctx" $.ctx) }}
</ul>
</li>
{{ else }}
<!-- No sub-tree. So, only add current entry -->
<li><a class="{{$class}}" href="{{ .URL }}" title="{{ .Name }}">{{.Name}}</a></li>
{{ end }}
{{ end }}
|
参考
我现在的思路
posts和topic是两套页面模板
第一次尝试
添加新的taxonomy
1
2
3
4
|
[taxonomies]
category = "categories"
series = "series"
tag = "tags"
|
修改几篇文章为series的
给series添加模板
关于搜索和排序
1
|
where COLLECTION KEY [OPERATOR] MATCH
|
比如
1
2
3
4
5
6
7
|
// 搜索所有页面参数series和本页面有重合的
where .Site.RegularPages.ByDate.Reverse "Params.series" "intersect" .Params.series
// $section是一个集合
{{ $sections:= site.Data.sections }}
where (sort $sections "section.weight") ".section.enable" true
|
where
+ 任意的集合 + 条件关键字 + [操作符(默认是等于)] + 条件
自定义输出格式
参考: Custom Output Formats | Hugo (gohugo.io)
比如想要输出markdown格式
添加一个模板,比如single.md
然后文章的frontmatter添加
1
2
3
|
outputs:
- html
- MarkDown
|
之后就可以通过http://xxx/post/xx-xxxx/index.md
进行访问了
debug
1
|
{{ printf "%#v" $.Site }}
|
分组GroupBy
参考: Lists of Content in Hugo | Hugo (gohugo.io)
- 可以按照section分组
- 可以按照页面参数
- 时间
- 自定义参数
主要有3种形式
第一种,固定关键词,像GroupByDate
第二种,关键词(分组对象)是参数, 比如 GroupBy "section"
第三种,自定义的Page参数,比如.Pages.GroupByParam "param_key"
和.Pages.GroupByParamDate "param_key" "2006-01"
分组之后会context里得到一个Key
, 这个值就是组的名称
1
2
3
4
5
6
7
8
9
10
11
12
|
<!-- Groups content according to content section. The ".Key" in this instance will be the section's title. -->
{{ range .Pages.GroupBy "Section" }}
<h3>{{ .Key }}</h3>
<ul>
{{ range .Pages }}
<li>
<a href="{{ .Permalink }}">{{ .Title }}</a>
<div class="meta">{{ .Date.Format "Mon, Jan 2, 2006" }}</div>
</li>
{{ end }}
</ul>
{{ end }}
|
或者,可以利用section的_index.html
来取得title替代key
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<!-- Groups content according to content section.-->
{{ range .Pages.GroupBy "Section" }}
<!-- Checks for existence of _index.md for a section; if available, pulls from "title" in front matter -->
{{ with $.Site.GetPage "section" .Key }}
<h3>{{.Title}}</h3>
{{ else }}
<!-- If no _index.md is available, ".Key" defaults to the section title and filters to title casing -->
<h3>{{ .Key | title }}</h3>
{{ end }}
<ul>
{{ range .Pages }}
<li>
<a href="{{ .Permalink }}">{{ .Title }}</a>
<div class="meta">{{ .Date.Format "Mon, Jan 2, 2006" }}</div>
</li>
{{ end }}
</ul>
{{ end }}
|
关于Content Type
经常在筛选条件里看到类似where ...... "Type" "post"
,想知道Type
是个什么东西
Content Types | Hugo (gohugo.io)
Type
作为Page的一个参数,可以在Page Variables | Hugo (gohugo.io)里查到
官方文档的解释是:
content type是组织内容的一种方式,hugo解析它的方式有两种,第一种从front matter里的type
参数获得,
第二种是如果没有设置type参数,那么就从文件夹的物理路径来解析,比如 content/blog/my-first-event.md
这个
页面的type在没有设置type的frant matter参数的时候,就是blog
Data
经常看到.Site.Data.Sections
,查不到官方的用法,最后在这里找到Setting custom meta desc and title for a section - support - HUGO (gohugo.io)
自定义数据
这个data是指一个自定义的数据存储文件夹, 在根目录下的data文件夹(会覆盖theme的),或者themes/<THEME>/data
这种用法是建立在用户自定义了data数据的基础上,参考Data Templates | Hugo (gohugo.io)
比如如果你建立了/data/sections.toml
1
2
3
4
5
6
7
|
[about]
title = "About Me"
description = "Lots of juicy details"
[mysection]
title = "My Section"
description = "My section description"
|
那就可以通过$.Site.Data.sections.about.title
来访问到"About Me"这条数据
如果是这么建立**/data/section/about.toml**, 也可以
1
2
|
title = "About Me"
description = "Lots of juicy details"
|
访问方式为$.Site.Data.section.about.title
,几乎没有区别
页面的数据
另外一个Data出现的地方多是一些特殊的页面里的变量,比如taxonomy
,参考Taxonomy Variables | Hugo (gohugo.io)
-
.Data.Plural 这个就是分类法的复数名称, 比如tags
对应的就是tags
-
.Data.Pages 这个分类法下面的所有页面
.Kind
为term
.Title
为分类名称,为每一个分类的具体名称,如css
或git
等等
.Pages
为分类下的所有文章实体页面,即这个Page实体是一个分类
.IsPage
为false
.IsNode
为true
.Type
为分类法的名称,都一样,如tags
-
.Data.Terms 这个分类法的items, 比如tags这个分类法的下面的所有标签名称
Taxonomy和Taxonomy Term
Taxonomy指的是分类法本身, 比如tag
,Term是分类的名称,比如css
拿情境举例, 我们最常用的分类法为category和tag, Taxonomy就是categories或者tags,term就是他们下面的具体分类
即使不写如下配置,系统默认的配置等同于
1
2
3
|
[taxonomies]
category = "categories"
tag = "tags"
|
Taxonomy对象
这个对象是一个map[string]WeightedPages
, 也就是具体的一个分类和page的列表,此对象还有一些方法可用
Taxonomy对象的方法
.Get(term)
Returns the WeightedPages for a term.
.Count(term)
The number of pieces of content assigned to this term.
.Alphabetical
Returns an OrderedTaxonomy (slice) ordered by Term.
.ByCount
Returns an OrderedTaxonomy (slice) ordered by number of entries.
.Reverse
Returns an OrderedTaxonomy (slice) in reverse order. Must be used with an OrderedTaxonomy.
.Site.Taxonomies
官网上的解释,Taxonomy Variables | Hugo (gohugo.io)
The .Site.Taxonomies
variable holds all the taxonomies defined site-wide. .Site.Taxonomies
is a map of the taxonomy name to a list of its values (e.g., "tags" -> ["tag1", "tag2", "tag3"]
). Each value, though, is not a string but rather a Taxonomy variable.
就是说首先是一个map类型,一个分类法名称对应的是该名称下的分类,每一个值是一个taxonomy
如果想在非标准的模板下使用分类,比如业务场景需要在当前页面列出所有分类,可以用这个站点的全局变量
它是一个map类型,所以用法可以是下面几种
第一种, 在已知分类法的情况下,比如我们有tags这个分类法
1
2
|
{{ $tagsTaxonomy := .Site.Taxonomies.tags }}
{{ $tagsTaxonomy.Get "css" }}
|
这样可以得到所有css分类下的page list
第二种, 适合用在非私人的模板上,未知到底有多少分类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
{{ range $taxonomyname, $taxonomy := .Site.Taxonomies }}
{{ $taxonomy.Get "css" }}
{{ end }}
<!-- 或者下面的示例 -->
<section>
<ul>
{{ range $taxonomyname, $taxonomy := .Site.Taxonomies }}
<li><a href="{{ "/" | relLangURL}}{{ $taxonomyname | urlize }}">{{ $taxonomyname }}</a>
<ul>
{{ range $key, $value := $taxonomy }}
<li> {{ $key }} </li>
<ul>
{{ range $value.Pages }}
<li><a href="{{ .Permalink}}"> {{ .LinkTitle }} </a> </li>
{{ end }}
</ul>
{{ end }}
</ul>
</li>
{{ end }}
</ul>
</section>
|
注意:某个分类法下的集合,其实是一个列表,列表里是page,比如上面的例子$key
只是个索引值
References
参考:
section模板
当访问/post
这样的url的时候,默认的模板是section.html
,这里有一个要注意的地方,如果在一个section的子目录里有_index.md
那么这个子文件夹下的所有文件将不会在section的list里显示,取而代之的是显示那个_index.md
的标题,相当于给了一个索引页.
section
默认list
的找模板的顺序是
- layouts/posts/posts.html.html
- layouts/posts/section.html.html
- layouts/posts/list.html.html
- layouts/posts/posts.html
- layouts/posts/section.html
- layouts/posts/list.html
- layouts/section/posts.html.html
- layouts/section/section.html.html
- layouts/section/list.html.html
- layouts/section/posts.html
- layouts/section/section.html
- layouts/section/list.html
- layouts/_default/posts.html.html
- layouts/_default/section.html.html
- layouts/_default/list.html.html
- layouts/_default/posts.html
- layouts/_default/section.html
- layouts/_default/list.html
也就是说,如果发布的文章是posts/xxx/xxx
, 如果要访问/posts/
展示所有的posts,模板的使用就是上面的顺序
如果发现内容不正确,那么需要加入一个_index.md
section
在content下的第一层目录叫做root section
如果section想要有子目录,那么要在子目录下定义_index.md
Kind
每个page都有.Kind
主要有以下 page, home, section, taxonomy 和term
关于分类法Taxonomy的模板
一个分类法所有分类的集合页面模板,在无命名和位置分类法(或者未定义)的情况下,默认的查找顺序是
1
2
3
|
layouts/_default/terms.html
layouts/_default/taxonomy.html
layouts/_default/list.html
|
分类下的页面列表模板的默认查找顺序是
1
2
3
|
layouts/_default/term.html
layouts/_default/taxonomy.html
layouts/_default/list.html
|
为了防止概念混淆,我现在习惯这样定义模板
1
2
3
|
layouts/_default/terms.html 某个分类法下所有分类列表页
layouts/_default/term.html 某个分类下所有页面的列表页
layouts/series/terms.html 确定的分类法series下的所有分类的列表页(比如所有专题的集合页面)
|
字符串连接
1
|
{{ $levelClassName := delimit (slice "menu-" $level) "" }}
|
判断两个数组的交集
比如搜索分类或者标签是否含有xxx
1
|
{{ $startPage := where (where $ctx.Site.RegularPages "Params.series" "intersect" (slice $term)) "Params.seriesStartPage" true }}
|
从dict里取值
如果为止的key,可以用index
来实现
1
|
{{ index $dict "mykey" }}
|




问题
section下的子目录用什么模板
section的子目录还是用section的模板,前提条件是子目录必须有_index.md
,否则就是空白页面