Hugo Template Introduction

2021-05-05 约 6148 字 预计阅读 13 分钟

Hugo模板使用介绍,hugo的主题制作最主要的部分就是模板,模板的基础部分包含代码块,注释,变量,包含的方式等等.

1. 基础部分

参考: Introduction

1.1. 访问预定义变量

1
2
{{ .Title }}
{{ $address }}

1.2. 访问方法

用空格分开参数

1
2
3
{{ FUNCTION ARG1 ARG2 .. }}
示例
{{ add 1 2 }}

1.3. 访问对象的方法或者属性

.来进行访问

1
{{ .Params.bar }}

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条件判断

关键字

  • if
  • else
  • with
  • or
  • and
  • not

在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 }}

andor

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

11. Comments注释

{{/* and */}}

1
Bonsoir, {{/* {{ add 0 + 2 }} */}}Eliott.

html注释

1
{{ printf "<!-- Our website is named: %s -->" .Site.Title | safeHTML }}

12. Hugo Parameters

  • 站点配置里的元数据
  • front matter

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 &#xA9; 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类页面模板上是支持分页的,而且支持简单的whereorder,类似sql

List页面都有一个默认的变量.Paginator

配置

关于如何实现专题页面

效果像一个文档专题,有自己的导航,甚至样式,要求

  • 导航不是来之配置而是来自相关的文章

  • 导航可以多级分布,其实有两级就够了

  • 专题可以任意添加,可以用一套模板就够了

  • 专题的文章无需特殊,来自post即可

当文章多了(或者说文章是比较有结构性的,可以整理成系列), 比如我想将git, css, 或者python, 单独建专题,

而不是简单的流式blog,希望有下面这种专题形式

image-20210526071953181

继续研究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
section

关于搜索和排序

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

1
{{ .RawContent }}

然后文章的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 这个分类法下面的所有页面

    • .Kindterm
    • .Title为分类名称,为每一个分类的具体名称,如cssgit等等
    • .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的找模板的顺序是

  1. layouts/posts/posts.html.html
  2. layouts/posts/section.html.html
  3. layouts/posts/list.html.html
  4. layouts/posts/posts.html
  5. layouts/posts/section.html
  6. layouts/posts/list.html
  7. layouts/section/posts.html.html
  8. layouts/section/section.html.html
  9. layouts/section/list.html.html
  10. layouts/section/posts.html
  11. layouts/section/section.html
  12. layouts/section/list.html
  13. layouts/_default/posts.html.html
  14. layouts/_default/section.html.html
  15. layouts/_default/list.html.html
  16. layouts/_default/posts.html
  17. layouts/_default/section.html
  18. 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" }}

e713ed70-1

image-20210622152754779

R1ee1db8ba0f4ae9cec4de9ccc5681f5e

See the source image

问题

  • section的子目录用什么模板
  • Kind和Type
  • 有哪些页面都有自己的哪些function
  • uglyUrl是干什么用的
  • resources.PostProcess

section下的子目录用什么模板

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

TAG: cms template
文章作者 : Cocding