hugo中文教程

常用命令列表

  • hugo server -p 1316
  • hugo
  • hugo help
  • hugo new
  • hugo new site
  • hugo new theme

常用命令详细说明

hugo server

hugo自带一个web服务器, 运行hugo server后可以通过 http://localhost:1313 来访问静态网站. 不需要自己去构建服务器环境, 还是很方便的.

下面是hugo server常用的参数, 注意大小写:
-p 端口: 修改默认端口
-D: 在使用server预览网站时, draft属性为ture的草稿文件是不会生成预览的. 添加-D后可以预览草稿文件.

hugo

hugo本身就是一个命令, 他的作用就是生成静态网站, 默认在生成的静态文件保存在public目录中. 也可以指定路径.

hugo help

通过hugo help命令可以获取hugo命令行的帮助文档. 下面会对每个命令做更详细的一些介绍. 在开始之前需要先在本机安装hugo, 并在环境变量中配置好hugo.

hugo new site

创建一个新的网站(骨架), 例如: hugo new site mysite , 他会在当前目录下创建一个mysite的文件夹, 并在其中填充必须的必须的目录和文件. 也可以指定具体的路径来创建新的网站. 下面是mysite文件夹的结构

├─archetypes
├─content
├─data
├─layouts
├─resources
  └─_gen
      ├─assets
      └─images
├─static
└─themes
    └─study-theme
        ├─archetypes
        ├─layouts
          ├─partials
          └─_default
        └─static
            ├─css
            └─js

content文件夹里面就是用于生成网站的”原材料”, themes里面放的是网站的UI, hugo就是靠这些来创建静态网站的.

hugo new

添加网站内容. 例如: hugo new about.md, 他会在content目录下生成一个about.md的文件, 根据这个文件可以生成对应的静态页面. 可以在about.md前面添加对应的路径, 但文件会以content为根目录, 也就是说所有添加新文件都会存放在content目录下面.

hugo new theme

为网站添加UI, 也就是模板文件/主题文件. 例如: hugo new theme mytheme. 这会在themes目录下创建一个mytheme目录, mytheme目录中会默认添加一些基本的文件结构. 所有的模板/主题文件都会保存在themes目录中.

Hugo的工作原理

作为普通的作者如果知道文章内容如何转化成网站页面的, 会有利于更好的使用hugo. 如果还想修改一下页面布局样式, 那就更有必要知道hugo的工作原理了.

基本概念 文章, 页面, 模板

文章 文章就是作者需要撰写的内容, 他以markdown格式的文件存放在content目录下面. 我们既可以通过命令行的方式创建文章 hugo new about.md, 也可以通过手工的方式在content创建. 通常我们把单独的文章内容放在content目录下面, 同一类型的文章内容放在content的子目录下面, 这样做hugo会根据子目录下的内容自动生成列表内容.

页面 页面就是通过hugo 最终生成的静态网站中的html页面. 页面是由两部内容合成的, 即: 页面 = 文章 + 模板. hugo会根据一定的规制去寻找文章对应的模板页面, 从而生成页面.

模板 模板页面存放在两个地方

├── layouts
└── themes
    └── mytheme
        └── layouts
            ├── 404.html             // 404页面模板
            ├── _default
            │   ├── baseof.html      // 默认的基础模板页, 使用的方式是'拼接', 而不是'继承'.
            │   ├── list.html        // 列表模板  
            │   └── single.html      // 单页模板
            ├── index.html           // 首页模板
            └── partials             // 局部模板, 通过partial引入
                ├── footer.html
                ├── header.html
                └── head.html 

layouts目录中默认为空, 但layouts目录的优先级高于themes/THEME/layouts目录, 所以我们可以在layouts目录下创建相同结构的文件来覆盖themes/THEME/layouts下面的设置

mytheme为模板名称, 可以通过config.toml文件设置要使用的模板 theme = “mytheme”. 通过hugo new theme 模板名称来创建新的模板.

content目录结构和URL的对应关系 其实也可以叫文章和页面的对应关系.

└── content
    ├── _index.md          // [home]            <- https://example.com/ **
    ├── about.md           // [page]            <- https://example.com/about/
    ├── posts               
    |   ├── _index.md      // [section]         <- https://example.com/posts/ **         
    |   ├── firstpost.md   // [page]            <- https://example.com/posts/firstpost/
    |   ├── happy           
    |   |   ├── _index.md  // [section]         <- https://example.com/posts/happy/ **
    |   |   └── ness.md    // [page]            <- https://example.com/posts/happy/ness/
    |   └── secondpost.md  // [page]            <- https://example.com/posts/secondpost/
    └── quote   
        ├── _index.md      // [section]         <- https://example.com/quote/ **           
        ├── first.md       // [page]            <- https://example.com/quote/first/
        └── second.md      // [page]            <- https://example.com/quote/second/

// hugo默认生成的页面, 没有对应的markdown文章 分类列表页面 // [taxonomyTerm] <- https://example.com/categories/ ** 某个分类下的所有文章的列表 // [taxonomy] <- https://example.com/categories/one-category ** 标签列表页面 // [taxonomyTerm] <- https://example.com/tags/ ** 某个标签下的所有文章的列表 // [taxonomy] <- https://example.com/tags/one-tag ** 从对应关系来看作者创建的文章路径, 会一一对应的转化成网站的URL,也就是页面. 所以作者应以反映所呈现网站结构的方式进行组织content的目录结构.

中括号[]中标注的是页面的kind属性, 他们整体上分为两类: single(单页面 - page) 和 list(列表页 - home, section, taxonomyTerm, taxonomy).

content目录下的所有_index.md可以用来生成对应的列表页面, 如果没有这些markdown文件, hugo也会默认生成对应的页面. 有这些markdown文件的话, hugo会根据文件里面的FrontMatter的设置生成更个性的页面.

页面和模板的对应关系 页面和模板的应对关系是根据页面的一系列的属性决定的, 这些属性有: Kind, Output Format, Language, Layout, Type, Section. 他们不是同时起作用, 其中kind, layout, type, section用的比较多.

kind: 用于确定页面的类型, 单页面使用single.html为默认模板页, 列表页使用list.html为默认模板页, 值不能被修改 section: 用于确定section tree下面的文章的模板. section tree的结构是由content目录结构生成的, 不能被修改, content目录下的一级目录自动成为root section, 二级及以下的目录, 需要在目录下添加_index.md文件才能成为section tree的一部分. 如果页面不在section tree下section的值为空 type: 可以在Front Matter中设置, 用户指定模板的类型. 如果没设定type的值, type的值等于section的值 或 等于page(section为空的时候) layout: 可以在Front Matter中设置, 用户指定具体的模板名称. 可以使用模板属性来查看这些属性的具体值

{{.Kind}}
{{.Section}}
{{.Type}}

从层次上hugo中的模板分为三个级别的, hugo依据从上到下的顺序一次查找模板,直到找到为止.

特定页面的模板 应对某一类页面的模板 应对全站的模板: 存放在_default目录下面的list.html 和 single.html页面 后面会根据kind属性的值, 分别介绍各种模板.

Hugo模板的基本语法

hugo使用的是go语言自带的模板引擎, 模板的标签为{{}}, {{}}中包含的内容叫’动作’(action).

动作–action 动作分为两种类型

数据求值 控制结构 求值的结果会直接输出到模板中, 控制结构主要包含条件, 循环, 函数调用等.

点.

{{.}}
点`.`代表传递给模板的数据, 表示当前模板的上下文, 他可以是go语言中的任何类型, 如: 字符串, 数组, 结构体等.

注释

{{/* comment */}}

空格处理

// 清除 pipeline 前后的空格
{{- pipeline -}}

// 清除 pipeline 前面的空格
{{- pipeline }}

// 清除 pipeline 后面的空格
{{ pipeline -}}
变量
1
{{$变量名 := "值"}}
给局部变量赋值使用 :=, 这是golang的语法

条件判断

{{if pipeline}} T1 {{end}} 
如果pipeline为空则不会输出任何结果, 否则输出T1.
下面这些情况pipeline的值为空, false, 0, 值为nil的指针或接口, 长度为0的数组, 切片, map和字符串

{{if pipeline}} T1 {{else}} T0 {{end}}
如果不为空则输出T1, 否则输出T0

{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}

循环语句

{{range pipeline}} T1 {{end}}
pipeline的值必须是数组, 切片, map, channel. 
如果pipeline的长度为0则不会输出任何结果. 否则设置点`.`为数组, 切片, map的遍历值, 输出T1.

with 重设点.的值

{{with pipeline}} T1 {{end}}
如果pipeline的值为空, 点`.`的值不受影响,不输出任何结果
否则点`.`的值设置成pipeline的值, 输出T1

{{with pipeline}} T1 {{else}} T0 {{end}}
如果pipeline的值为空, 点`.`的值不受影响,输出T1
否则点`.`的值设置成pipeline的值, 输出T0
这就相当于重新创建了新的上下文环境. 在{{with}}内的点(.)的值为新的值, 也就是{{with pipeline}}传进来的值.

模板的嵌套
在编写模板的时候, 常常将公用的部分单独做成一个模板, 如每一个页面都有导航栏, 页首, 页脚等, 然后在需要的地方导入这些模板,一般会先编写一个基础模板, 然后在基础模板中引入子模板, hugo默认的基础模板页是_default/baseof.html.

define
``
{{define "name"}} T1 {{end}}

定义一个特定名称的模板 template

{{template "name"}}
引入指定名称的模板, 不传入任何数据.

{{template "name" pipeline}}
引入指定名称的模板, 设置模板上下文点`.`的值为pipeline的值

block

{{block "name" pipeline}} T1 {{end}}
定义特定名称的模板, 并在当前位置引入该名称的模板, 模板的上下文点`.`的值为pipline的值, 
如果该名称的模板未实现(不存在), 则输出T1
就相当于在基础模板页中定义了一个子模板占位符.
hugo中模板嵌套规则
hugo中引入模板改用partial, template只用来引入内部模板. partial通过路径的方式引入模板, 被引入的子模板不在需要定义模板名.

如果模板页面通过define定义了模板名称, 则该子模板会输出到基础模板页baseof.html中block定义的对应名称的位置.

partial partial引入模板时的查找路径只会在下面两个地方

{{ partial "<PATH>/<PARTIAL>.html" . }}   // 语法
layouts/partials/*<PARTIALNAME>.html
themes/<THEME>/layouts/partials/*<PARTIALNAME>.html

baseof.html baseof.html为hugo的默认基础模板页, 主要用于block语法. baseof.html存放在以下两个位置

layouts/_default/baseof.html
themes/<THEME>/layouts/_default/baseof.html
hugo先找到需要解析的模板, 如果模板中有{{define "name"}} T1 {{end}}, 则再去加载baseof.html基础模板, 并对比baseof.html中的{{block "name" pipeline}} T1 {{end}}, 如果找到相同的名称则在block处输出define中的T1, 如果没有找到相同的名称, 则在block处输出block中的T1

Hugo中列表页模板概述

列表页用于在单个页面中集中展示某一类的信息, 如: 某个section下的所有文章, 或者 列出所有的tags, 又或者列出某个标签下的所有文章.

hugo中的列表类页面包括: site homepage(首页), section page(文章目录页), taxonomy list(某一分类的文章列表), taxonomy terms list(所有的分类)

一切皆页面 在hugo 0.18 版中提出了 “everything in Hugo is a Page” 的概念, 这意味着列表页面可以有关联的content files, 即_index.md文件. content目录下的_index.md和首页相关, 各个子目录下的_index.md和对应的section page相关, taxonomy list 和 taxonomy terms list需要在content目录下面创建特定名称的目录(tags或categories)并在里面添加_index.md文件

示例

├── content
|   ├── posts
|   |   ├── _index.md
|   |   ├── post-01.md
|   |   └── post-02.md
|   └── quote
|   |   ├── quote-01.md
|   |   └── quote-02.md

假设content/posts/_index.md具有以下内容

---
title: 新的开始
date: 2019-11-02
---
今天, 我开始了新的blog之旅

这得益于发现了hugo这么好的静态博客系统
现在可以在seciton模板中访问_index.md的内容

// 模板页的位置 layouts/_default/section.html

{{ define "main" }}
<main>
    <article>
        <header>
            <h1>{{.Title}}</h1>
        </header>
        <!-- "{{.Content}}" 从posts/_index.md中读取内容 -->
        {{.Content}}
    </article>
    <ul>
    <!-- 遍历content/posts/*.md -->
    {{ range .Pages }}
        <li>
            <a href="{{.Permalink}}">{{.Date.Format "2006-01-02"}} | {{.Title}}</a>
        </li>
    {{ end }}
    </ul>
</main>
{{ end }}

上面的代码输出以下html代码

<main>
    <article>
        <header>
            <h1>新的开始</h1>
        </header>
        <p>今天, 我开始了新的blog之旅</p>
        <p>这得益于发现了hugo这么好的静态博客系统</p>
    </article>
    <ul>
        <li><a href="/posts/post-01/">Post 1</a></li>
        <li><a href="/posts/post-02/">Post 2</a></li>
    </ul>
</main>

注意: _index.md不是必须的, 如果没有找到_index.md,hugo会使用一些默认值

默认列表模板 这些列表页在layouts/_default/和themes//layouts/_default/下都有自己专属的默认模板, 其中section page和taxonomy list还可以公用list.html模板.

layouts/_default/index.html       //  site homepage
layouts/_default/section.html     //  section page
layouts/_default/taxonomy.html    //  taxonomy list
layouts/_default/terms.html       //  taxonomy terms list

内容排序 列表页是对某一类信息集中展示, hugo提供了一些排序的方法. 下面列出一些比较常用的方法.

优先级: Weight > Date > LinkTitle > FilePath

By Weight 根据优先级排序, weight的值越小排序越靠前

By Date 更具创建日期排序, 最新创建的内容排在前面.

Reverse Order 倒序

<ul>
    {{ range .Pages.ByDate.Reverse }}
        <li>
            <h1><a href="{{ .Permalink }}">{{ .Title }}</a></h1>
            <time>{{ .Date.Format "Mon, Jan 2, 2006" }}</time>
        </li>
    {{ end }}
</ul>

SingePageTemplate查找顺序

单页面模板查找顺序要注意section, type属性的关系, section属性由content目录下面的结构决定的, type属性总是会有一个值, 没有明确设置的而内容页面又在section tree下的时候其值为section属性的值, 如果没有明确设置内容页面又没有在section tree下时,type的值为page

section下单页面模板的查找顺序

layouts/section的值/layout的值.html 
layouts/section的值/single.html 

layouts/_default/single.html

非section下单页面模板的查找顺序

layouts/page/layout的值.html 
layouts/page/single.html  //type的默认值

layouts/_default/single.html

设置了type属性的单页面模板的查找顺序

layouts/type的值/layout的值.html 
layouts/type的值/single.html 

layouts/_default/single.html

Hugo中SectionTemplate的查找顺序

section template为列表类型的模板, 用来展示section tree中某个的节点文章列表, Kind可以轻松地与模板中的where函数结合使用,以创建种类特定的内容列表.

section模板的查找顺序

layouts/section的值/section的值.html 
layouts/section的值/section.html 
layouts/section的值/list.html 

layouts/section/section的值.html 
layouts/section/section.html 
layouts/section/list.html 

layouts/_default/section的值.html 
layouts/_default/section.html 
layouts/_default/list.html

设置type的section模板的查找顺序

layouts/type的值/section的值.html 
layouts/type的值/section.html 
layouts/type的值/list.html 

layouts/section的值/section的值.html 
layouts/section的值/section.html 
layouts/section的值/list.html 

layouts/section/section的值.html 
layouts/section/section.html 
layouts/section/list.html 

layouts/_default/section的值.html 
layouts/_default/section.html 
layouts/_default/list.html

Hugo中TaxonomyTemplate的查找顺序

tags和categories是hugo默认会创建的两种分类, 如果要手工创建分类可以在config文件中配置

[taxonomies]
  category = "categories"
  tag = "tags"

tags和categories对应的url

http://域名/tags
http://域名/categories

如果不希望hugo创建任何分类, 配置config中的disableKinds属性

disableKinds = [“taxonomy”, “taxonomyTerm”] taxonomy list(某一分类的文章列表)), taxonomy terms list(所有的分类)

Taxonomy Terms List Pages

layouts/categories/category.terms.html 
layouts/categories/terms.html 
layouts/categories/list.html 

layouts/taxonomy/category.terms.html 
layouts/taxonomy/terms.html 
layouts/taxonomy/list.html 

layouts/category/category.terms.html 
layouts/category/terms.html 
layouts/category/list.html 

layouts/_default/category.terms.html 
layouts/_default/terms.html 
layouts/_default/list.html

Taxonomy List Pages

layouts/categories/category.html 
layouts/categories/taxonomy.html 
layouts/categories/list.html 

layouts/taxonomy/category.html 
layouts/taxonomy/taxonomy.html 
layouts/taxonomy/list.html 

layouts/category/category.html 
layouts/category/taxonomy.html 
layouts/category/list.html 

layouts/_default/category.html 
layouts/_default/taxonomy.html 
layouts/_default/list.html

基础模板–Baseof.html

基础模板页的文件名字为baseof.html 或 -baseof.html 在基础模板页中使用block定义了一个占位符, 当模板页使用了一个基础模板页时, 模板页的解析后的内容会嵌入到基础模板页面中block的位置

基础模板页语法:

{{block “name” pipeline}} T1 {{end}} 模板页语法

{{ define “main” }} T1 {{ end }} 示例: baseof.html的内容如下

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>baseof基础模板页</title>
</head>
<body>
    <div id="content">
        {{- block "main" . }}{{- end }}
        <!-- . 点表示从基础模板页面传递到模板页面的变量 -->
    </div>
</body>
</html>

网站首页引用基础模板页

<!-- block定义的占位符是 'main', 所以这里需要定义名为 'main'的模板 -->
{{- define "main" -}}
  <section id="posts" class="posts">
    被define 和 end 包裹的内容会插入到baseof.html文件的{{- block "main" . }}{{- end }}位置.
  </section>
{{- end -}}

除了要在基础模板页使用block外, 基础模板页的 命名 和 存放位置 也要征询一定的规制

基础模板页的存放位置及命名

/layouts/section/<TYPE>-baseof.html
/themes/<THEME>/layouts/section/<TYPE>-baseof.html

/layouts/<TYPE>/baseof.html
/themes/<THEME>/layouts/<TYPE>/baseof.html

/layouts/section/baseof.html
/themes/<THEME>/layouts/section/baseof.html

/layouts/_default/<TYPE>-baseof.html
/themes/<THEME>/layouts/_default/<TYPE>-baseof.html

/layouts/_default/baseof.html
/themes/<THEME>/layouts/_default/baseof.html

表示自定义模板的名称. 表示页面的type值. 注意: 模板页和基础模板页总是在同一个目录下面, 如果当前目录下面没有, 会到_default目录下面去找. 如果在本地调试, 可以加上 –debug 参数, 能查看每个模板页的基础模板页路径

DEBUG 2019/11/14 04:46:32 Add template file: name "index.html", baseTemplatePath "", path "index.html"
DEBUG 2019/11/14 04:46:32 Add template file: name "section/single.html", baseTemplatePath "section\\baseof.html"

自定义数据 Hugo中的数据库

虽然hugo创建的是静态网站, 所以不会连接像mysql这样的数据库来获取数据, 但在有些地方依然需要获取一些预先定义好的数据, hugo提供了一些方法来解决这些问题.

data目录 在data目录下创建json | yaml | toml 格式的文件. 通过 .Site.Data变量以MPA的方式访问这些数据. 也就是MAP的结构组织目录和文件名的. 具体的可以输出.Site.Data来查看

例如在data目录中存放了两个用户的信息:

data/users/jim.toml data/users/tom.toml 文件的内容为:

name = "Jim"
age = 22
address = [
    "地址一",
    "地址二"
]

在模板中使用这些数据:

{{$users := .Site.Data.users}}
{{$user_jim := .Site.Data.users.jim}}
<div>jim.tom文件中的姓名: {{$user_jim.name}}</div>
<div>输出users目录下所有文件的内容: {{$users}}</div>

外源数据 hugo还可以通过getJSON和getCSV两个函数加载外部数据, 这两个函数是模板函数. 在外部数据加载完成以前, hugo会暂停渲染模板文件.

语法:

{{ $dataJ := getJSON "url" }}
{{ $dataC := getCSV "separator" "url" }}

带可变参数语法:

{{ $dataJ := getJSON "url prefix" "arg1" "arg2" "arg n" }}
{{ $dataC := getCSV  "separator" "url prefix" "arg1" "arg2" "arg n" }}

getCSV的第一个参数为分隔符.

示例:

  <table>
    <thead>
      <tr>
      <th>Name</th>
      <th>Position</th>
      <th>Salary</th>
      </tr>
    </thead>
    <tbody>
    {{ $url := "https://example.com/demo.csv" }}
    {{ $sep := "," }}
    {{ range $i, $r := getCSV $sep $url }}
      <tr>
        <td>{{ index $r 0 }}</td>
        <td>{{ index $r 1 }}</td>
        <td>{{ index $r 2 }}</td>
      </tr>
    {{ end }}
    </tbody>
  </table>

这些外部的csv或json文件加载到本地后, 会缓存在特定的目录$TMPDIR/hugo_cache/. 也可通过–cacheDir参数指定新的缓存目录.或者通过–ignoreCache来禁止缓存.

FrontMatter和Config–hugo的另类数据源

作者编写的文章内容是hugo构建博客网站的主要数据来源, 但一个网站通常还需要其他的数据, 如 网站标题, 页面的SEO数据等, 但做为一个静态的博客系统, 这些数据是没办法存放在数据库的. FrontMatter和Config就是为解决这些问题的.

FrontMatter – 前置数据 FrontMatter添加在作者编写的文章内容前面的一段数据, 格式有yaml, toml, json. FrontMatter直译很难理解, 而他们都是以特定的数据格式出现在文章内容的顶部的, 为hugo生成静态页面提供数据来源的. 所以把他翻译为: 前置数据.

下面是前置数据的具体形式, yaml由—包裹, toml由+++包裹, json由{}包裹.

---
title: "FrontMatter和Config--hugo的另类数据源"
date: 2019-11-01T05:15:33+08:00
draft: true
author: "Suroppo"
tags: []
keywords: []
description: ""
---

文章内容…..

+++
title= "FrontMatter和Config--hugo的另类数据源"
date= 2019-11-01T05:15:33+08:00
draft= true
author= "Suroppo"
tags= []
keywords= []
description= ""
+++

文章内容…..

{
   "title": "FrontMatter和Config--hugo的另类数据源", 
   "date": "2019-11-01T05:15:33+08:00",
   "draft": true,
   "author": "Suroppo",
   "tags": [],
   "keywords": [],
   "description": ""
}

文章内容….. 在这些前置数据中, 有些是hugo默认的数据, 也由一些是用户自定义的数据. hugo把他们封装成模板变量, 以便在模板中使用.

Config config文件也同样支持yaml, toml, json三种数据格式. 默认config以文件的形式存放在hugo站点的根目录下面, 也可以存放在config目录下面, 另外还可以通过命令行的–config参数指定配置文件.

使用数据 对于如何使用这些数据, 因为涉及到模板的相关内容, 后面会写一篇模板的基本用法的文章. 来介绍如何使用这些数据

自定义hugo主题–概述

从一句话说起

everything in Hugo is a Page

这个是hugo的一个主旨, 意思是我们看到的每个静态页面都有一个对应的markdown文件.

hugo通常用来创建个人博客, hugo通过用户编写markdown文件来生成静态网站, 用户想在博客网站上添加什么页面, 只用编写对应的markdown文件就可以了.hugo根据markdown文件的类型寻找对应的页面模板来生成对应的静态页面. 一切就是这么简单. 如果想构建自己的网站主题, 我们只需要搞清楚markdown文件和模板页面的对应关系就可以了. 另外, 存放用户的markdown文件的目录结构一般就代表最终网站的结构.

hugo工作目录的结构

├── archetypes
│   └── default.md
├── config.toml
├── content
├── data
├── layouts
│   ├── _default
│   │   ├── list.html
│   │   └── single.html
│   └── index.html
├── public
│   ├── categories
│   │   └── index.xml
│   ├── index.xml
│   ├── sitemap.xml
│   └── tags
│       └── index.xml
├── resources
│   └── _gen
│       ├── assets
│       └── images
├── static
└── themes
    └── mytheme
        ├── archetypes
        │   └── default.md
        ├── layouts
        │   ├── 404.html
        │   ├── _default
        │   │   ├── baseof.html
        │   │   ├── list.html
        │   │   └── single.html
        │   ├── index.html
        │   └── partials
        │       ├── footer.html
        │       ├── header.html
        │       └── head.html
        ├── LICENSE
        ├── static
        │   ├── css
        │   └── js
        └── theme.toml

上面的目录结构就是hugo的工作目录, 或者叫网站骨架, hugo就是根据这里的东西生成网站的, 结构看上去很多, 但我们主要了解两个目录和一个文件就可以了.

content目录: 这个目录就是用户存放markdown文件的地方, 他的目录结构就是网站的结构 themes目录: 这个目录就是存放页面模板的地方, 所有的markdown文件在这里都有对应的模板页面 config.toml文件: 这个配置文件主要用来指导hugo如何生成网站的.比如生成网站时使用那个主题 创建主题的命令

hugo new theme <主题名称> # 实际使用的时候没有尖括号(<>) hugo会根据命令行中的模板名称, 在themes目录中创建同名的目录, 然后再目录中生成基本的模板文件.

文章目录 hugo这种模式的个人博客系统, 在一定程度上燃起了我写博客的积极性, 以前也写, 在一些博客平台上, 因为做技术的总是想搞个自己的博客, 也弄过一些开源的博客系统, 总是觉得很麻烦, 各种配置, 安装, 弄皮肤. 最后反而把写作这事情搞丢了, 真是有点本末倒置. 而hugo却可以让写作时丝般顺滑, 倒腾皮肤时随心所欲. 这个系列的文章就是在倒腾hugo的过程中的一些记忆, 前面有按hugo上的一些技术点写了些文章, 感觉自己都有点看不懂, 这次换了个角度, 从实际需求的角度写, 比如想定义首页的样式, 想定义文章页面的样式, 这样也许更能把思路理清, 也更加实用.

自定义hugo主题–概述 自定义hugo主题–从内容页开始 自定义hugo主题–内容列表页 自定义hugo主题–网站首页 自定义hugo主题–导航菜单 自定义hugo主题–标签和分类 文章中涉及到源码可以在这里查看: https://github.com/Suroppo/hugo-theme-demo

自定义hugo主题–从内容页开始

准备工作 创建网站骨架和主题目录

hugo new site study-hugo  # 创建网站骨架
cd study-hugo  
hugo new theme study-theme  # 创建主题目录

在config.toml文件中配置study-theme主题

theme = “study-theme” 添加第一篇博文

hugo new post/page1.md
# hugo new的命令格式
# hugo new [path]

这时content目录会变成下面这样

└── content
    └── post
        └── page1.md

我们打开page1.md文件, 发现文件本身并不是空的, 而是有一些默认的内容.

---
title: "Page1"  
date: 2019-11-11T09:53:40+08:00
draft: true
---

这些内容是由/archetypes目录中的内容原型决定的.

由 — 包裹的内容为”Front Matter”, FrontMatter中包含了一些预定义的变量, 这些变量的值可以在模板文件通过模板中的变量取得. —一下的内容为博文的实际内容.

title: 页面的标题 date: 页面的时间 draft: true表示当前页面是草稿页

我们给博文添加一些内容

---
title: "我们的第一篇博客"
date: 2019-12-05T09:53:40+08:00
draft: false
---
;博文正文
## 大家好!
新博客, 新气象, 愿大家有个好的开始.

.....

Good Luck! 为博文设计模板 /themes/study-theme/layouts/_default/single.html文件是所有内容页面默认的模板页面

single.html文件默认为空, 修改内容如下:

<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>{{.Title}}</title>
</head>

<body>
    <div id="post" class="post">
        <article>
            <header>
                <h1 class="post-title">{{ .Title }}</h1>
            </header>
            {{.Content}}
        </article>
    </div>
</body>

</html>

模板中的{{.Title}}和{{.Content}}都是模板页面级的变量, 他们的值来至内容页中”Front Matter”中对应的字段. 具体本示例中就是/content/post/page1.md中的内容

预览网站

hugo server -D 
# Web Server is available at http://localhost:1313/ (bind address 127.0.0.1)

服务器的默认地址是http://localhost:1313/, 端口号可以通过 -p 自定义端口 来修改.

content目录的结构就是网站的结构, 所以我们刚才添加的第一篇博客的url地址就是: http://localhost:1313/post/page1/

Front Matter中常用的变量 title: 内容标题 date: 该页面的时间, 一般用来存放页面的创建时间. draft: 如果true, 表示页面为草稿页, 内容不会呈现在网站中, 除非添加 –buildDrafts 给hugo命令. keywords: 页面的关键字 description: 内容描述, 主要用于SEO优化. weight: 列表页的文章排序, 值越小越靠前, 默认是按时间先后排序的, 也就是date中的值 模板页中常用的变量 .Title: 获取Front Matter中title的值 .Content: 获取文章的内容 .Date: 获取Front Matter中date的值 .Description: 获取Front Matter中description的值, 一般用于meta中的description字段 .Keywords: 获取Front Matter中keywords的值, 用于meta中的keyword字段 .Permalink: 获取页面的链接地址(URL) .Next: 下一个页面 .Prev: 上一个页面 .WordCount: 内容的字数 .ReadingTime: 阅读内容的预估时间 .Pages: 当前列表页面下的内容页面的集合, 该变量在内容页模板的上下文中值为nil .Site: 站点变量, 该变量下包含很多站点级别的属性和方法. 这些变量可以在官网找到 https://gohugo.io/variables/page/ 具体的含义可以逐步去试一下.

自定义hugo主题–内容列表页

内容页面的划分 根据内容页存放的位置, 内容页分为两种, 一种是存放在content根目录下面的内容页, 叫单页面(Single Page), 一种是存放在content子目录下的内容页, 叫章节页面(Section Page). 两者的区别是: 他们的Section属性不同, 章节页面的Section属性的值为页面所在的目录名, 单页面的Section属性的值为空字符串, Type属性为page.

这里给内容页面添加列表页主要是给章节页面添加列表页.

为所有的内容页面添加一个列表页. 在前一篇DEMO的基础上我们多添加了几篇博文, 和一个_index.md文件. content目录的结构如下

└── content
    └── post
        ├─ _index.md
        ├─ page1.md
        ├─ page2.md
        ├─ page3.md
        └─ page4.md

_index.md就是post目录下面所有博文的列表页. 它对应的默认模板文件在: layouts/_default/list.html.

现在修改list.html内容如下

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>列表页面</title>
</head>
<body>
    <!-- {{ with .Site.GetPage "/post" }}<a href="{{.Permalink}}">{{ .Title }}</a>{{ end }} -->
    {{  range .Pages }}
        <div>
            <a href=".Permalink">{{.Title}}</a>
        </div>
    {{  end  }}
</body>
</html>

range: 可以用来遍历集合, .Pages变量获取的是当前当前章节下说有的内容页的集合. 如本例中, 访问post章节下的列表页, 这时.Pages包含的就是post目录下的所有文章.

在遍历的时候, 每次循环的上下文就是每个内容页面, 内容页面模板中的变量都可以使用的.

为内容页面添加上一页和下一页 这里主要使用到了内容页模板中的 .NextInSection 和 .PrevInSection 变量, 他们表示当前章节中, 按时间倒序排列的文章集合的后一篇文章和前一篇文章.

<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>{{.Title}}</title>
</head>

<body>
    <div id="post" class="post">
        <article>
            <header>
                <h1 class="post-title">{{ .Title }}</h1>
            </header>
            {{.Content}}
            <br />

            <div>
                {{with .NextInSection}}
                <a href="{{.Permalink}}">前一页</a> 
                {{end}}
                {{with NextInSection}}
                <a href="{{.Permalink}}">后一页</a>
                {{end}}
            </div>
        </article>
    </div>
</body>

</html>

自定义hugo主题–网站首页

首页也属于列表页, 只是他是一个特殊的列表页. 如果没有给首页添加模板, 首页使用内容页的模板.

DEMO的目录结构

├─content
│  ├─news
│  └─post
├─data
├─layouts
├─static
└─themes
    └─study-theme
        ├─archetypes
        ├─layouts
        │  ├─index.html
        │  ├─partials
        │  └─_default
        └─static

首页模板的位置 /themes/study-theme/layouts/index.html 首页模板使用单独的模板, 模板的名字也是固定的 index.html. 也可以给首页添加一个markdown文件, 位置在content根目录下, /content/_index.md. 这个不是必须的.

在首页中展示各章节的文章列表 首页是整个网站的一个索引, 我们一般会在首页中展示各个板块的文章列表, 在本DEMO中我们添加了两个板块, 也就是/content目录中的news 和 post这两个章节目录, 现在我们把这两个章节的内容添加到首页中.

<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>网站首页</title>
</head>

<body>
    <h2> 新闻列表 </h2>
    {{ range where .Site.RegularPages "Section" "news" }}
    <div>
        <a href="">{{.Title}}</a>
    </div>
    {{ end }}

    <h2> 博文列表 </h2>
    {{ range where .Site.RegularPages "Section" "post" }}
    <div>
        <a href="">{{.Title}}</a>
    </div>
    {{ end }}
</body>

</html>

这里主要用到两点

.Site.RegularPages 表示网站下面的所有节点内容, 具体到本例, 就是news和post目录下的所有文章 where 是一个函数, 用来查询集合中符合条件的项目. where 的语法: where COLLECTION KEY [OPERATOR] MATCH

where .Site.RegularPages “Section” “news” 的含义就是, 查找出section属性的值为news的项目

自定义hugo主题–导航菜单

设置导航菜单的位置 在根目录下的 config.toml 文件中配置导航菜单, config 文件支持三种文件格式: toml, yaml, json, 可以使用自己熟悉的格式. 使用对应的格式需要修改对应的扩展名. 不推荐使用 JSON 格式, 因为 json 不支持注释, 在配置文件中添加注释是非常有必要的.

通过.Site.Menus 变量获取到设置的菜单信息, 下面以 toml 格式为例展示导航菜单的使用.

添加主导航菜单和页脚导航菜单

# 主导航菜单
name="首页"
url="/"
weight="1"
[[menu.main]]
name="博文"
url="/post"
weight="2"
[[menu.main]]
name="新闻"
url="/news"
weight="3"
[[menu.main]]
name="关于"
url="/about"
weight="4"

# 页脚导航菜单
[[menu.foot]]
name="网站首页"
url="/"
weight="4"
[[menu.foot]]
name="博文集合"
url="/post"
weight="3"
[[menu.foot]]
name="新闻列表"
url="/news"
weight="2"
[[menu.foot]]
name="关于我们"
url="/about"
weight="1"

获取菜单的代码如下”

<h2>主导航菜单</h2>
<ul>
  {{ range .Site.Menus.main }}
  <li><a href="{{.URL}}">{{.Name}}</a></li>
  {{ end }}
</ul>

<h2>页脚导航菜单</h2>
<ul>
  {{ range .Site.Menus.foot }}
  <li><a href="{{.URL}}">{{.Name}}</a></li>
  {{ end }}
</ul>

.Site.Menus后面接的是在 config.toml 中定义的表名

定义多级菜单

[[menu.main]]
name="首页"
url="/"
weight="1"
[[menu.main]]
name="博文"
url="/post"
weight="2"
[[menu.main]]
name="新闻"
url="/news"
weight="3"
[[menu.main]]
name="关于"
url="/about"
weight="4"
[[menu.main]]
name="子菜单"
weight="5"
[[menu.main]]
parent="子菜单"
name="子菜单1"
weight="2"
[[menu.main]]
parent="子菜单"
name="子菜单2"
weight="1"

获取菜单的代码如下:

<h2>主导航菜单</h2>

<ul>
  {{ range .Site.Menus.main }} 
  {{if .HasChildren}}
  <li>{{.Name}}</li>
  <ul>
    {{ range .Children }}
    <li>
      <a href="{{ .URL }}">{{ .Name }}</a>
    </li>
    {{ end }}
  </ul>
  {{ else }}
  <li><a href="{{.URL}}">{{.Name}}</a></li>
  {{end}} {{ end }}
</ul>

子菜单项比普通的菜单项多了一个属性parent, 用于设置父菜单的ID, 这里用的是Name的值, 如果name的值不重复, 一旦重复就会产生意想不到的效果. 正确的做法是使用Identifier的值.

导航菜单常用的属性 .Name: 菜单想的名称, 如果Name的值重复了, 要想使其生效, 需要为其设置Identifier属性 .Identifier: 菜单想的唯一标识, 值不能重复. .Weight: 用于设置菜单项的排序, 值越小排名越靠前. .Parent: 设置父菜单的ID. .URL: 设置菜单项直向的URL地址. 当行菜单常用的方法 .HasChildren: 判断是否包含子菜单. 如果有子菜单则返回true

Hugo 模板

模板变量 如果说模板是待填充的网页,则模板变量是用来填充模板的内容。Hugo 内置了许多可以在模板中访问的变量,这些变量可以分为以下几种类型

网站变量

通过网站变量,我们可以访问网站级别的配置和数据。

.Site.BaseURL 			配置文件中为网站指定的 basse URL
.Site.RSSLink 			网站的 RSS 链接
.Site.Taxonomies 		网站所有的分类标签
.Site.Pages				网站所有页面(仅含当前语言)
.Site.AllPages			网站所有页面(含多语言)
.Site.Params			配置文件中通过 params 定义的网站参数
.Site.Sections			网站所有 Section(也即网站的顶级目录)
.Site.Title				配置文件中为网站指定的 title
.Site.Author			配置文件中为网站指定的 author
.Site.Copyright			配置文件中为网站指定的 copyright
.Site.LastChange		网站最后更新时间,格式跟内容文档头部 date 保持一致
.Site.Data				网站自定义数据文件的访问接口
.Site.RegularPages		网站中所有常规页面
.Site.Files				网站所有源文件
.Site.Menus				网站所有菜单
.Site.LanguageCode		配置文件中为网站指定的 language code
.Site.DisqusShortname	配置文件中为网站指定的 disqus 评论id
.Site.GoogleAnalytics   配置文件中为网站指定的 google analytics tracking code
.Site.Permalinks		配置文件中为网站指定的 permalink format
.Site.BuildDrafts		配置文件中为网站指定的 build drafts
.Site.IsMultiLingual	网站是否支持多语言
.Site.Language			配置文件中指定的 language

页面变量

通过页面变量,我们可以访问内容文档级别的配置和数据。

.Title					内容文档的标题
.Content				内容文档的内容
.Date					内容文档的日期
.PublishDate			页面发布日期
.FuzzyWordCount			内容的近似字数
.WordCount				内容的字数
.Type					内容文档的内容类型
.URL					页面的相对 URL
.UniqueID				内容文档路径的md5值
.Weidht					内容文档中定义的排序权重
.Kind					页面类型
.Params					内容文档头部定义的任意元数据都可以通过 .Params 来访问(不同定义如何命名,均以字母小写的名字访问)
                      补充:网站变量中也有 .Site.Params 来定义网站参数,一般来说页面参数比网站参数更具体,
                      可以使用模板函数 $.Param "header_image" 来访问网站和页面的同名参数
.IsHome					页面是否为首页
.IsPage					是否为常规内容页面
.Next					下一个页面(根据页面发布日期)
.Prev					上一个页面(根据页面发布日期)
.NextInSection			当天Section中的下一个页面(根据页面分布日期)
.PrevInSection			当天Section中的上一个页面(根据页面分布日期)
.TableOfContents		页面目录
.Permalink				页面的永久链接
.RelPermalink			页面永久链接的相对路径
.RawContent				页面的 Markdown 内容,当想要在网站中集成https://github.com/gnab/remark时,就需要提取页面的 Markdown 内容了
.ReadingTime			页面大概需要花费的阅读时间
.Section				页面所在 Section
.Summary				页面摘要
.Truncated				摘要是否截断页面
.Description			描述
.Keywords				关键词
.LinkTitle				链接到当前页面时使用的 title
.ExpiryDate				页面失效日期
.Draft					页面是否为草稿
.IsTranslated			页面是否有多语言版本
.Translations			页面的多语言页面
.Lang					语言
.Language				语言对象

文件变量

当页面的生成来源于内容文档时,可以访问内容文档文件相关信息。

.File.Path				内容文档的相对路径,比如:content/posts/first.en.md
.File.Dir				内容文档所在目录
.File.LogicalName		内容文档文件名,比如:first.en.md
.File.TranslationBaseName 内容文档根文件名,比如:first
.File.Ext				内容文档扩展名,比如:md
.File.Lang				内容文档的语言

Hugo 变量

.Hugo.Generator			Hugo 版本号的 meta tag,例如:<meta name="generator" content="Hugo 0.15" />
.Hugo.Version			Hugo 二进制程序版本号

模板变量的作用域问题

单页模板、Section 列表模板以及 Taxonomy 列表模板均可以访问网站变量和页面变量,此外Taxonomy 列表模板可以访问代表其自身的 .Data.Singular 变量。

模板角色 模板文件混杂了 HTML 代码和模板标识符,用来设计网页布局的。Hugo 支持 Go 语言的 HTML 模板库来对网站进行布局规划,虽然模板文件本质上没有不同,可 Hugo 结合常用网站布局结构的需要将模板分为了几种角色,下面将依次介绍这些模板角色

也即页面类型

page home section taxonomy or taxonomy Term

rss sitemap robotsTXT 404

首页模板 Hugo 使用首页模板(homepage template)来渲染网站首页。一般来说网站首页同其它页面具有不一样的风格,因此需要专门为其使用特定的模板进行渲染。Hugo 在生成网站时,通常会依次从下面路径中查找首页模板,将找到的第一个文件作为首页模板:

/layouts/index.html
/layouts/_default/list.html
/layouts/_default/single.html
/themes/THEME/layouts/index.html
/themes/THEME/layouts/_default/list.html
/themes/THEME/layouts/_default/single.html

也即默认首页模板是 index.html ,当该文件不存在时,依次使用 list.html 和 single.html 来充当首页模板。另外首页模板中可以通过模板变量 .Data.Pages 来访问网站中所有内容文档,通常我们会遍历该变量在首页创建一个文档展示列表,不过Hugo 不会对模板的创建有任何限制,如何定义首页模板完全取决于自己。

单页模板 Hugo 使用单页模板(single template)来渲染内容文档。换句话说,内容文档的内容将嵌入单页模板设计好的网页结构中,以此生成网页。那么当生成静态网站时,Hugo 会使用哪个单页模板来渲染内容文档呢?Hugo 会依次从下面路径列表中查找可用的单页模板,将找到的第一个单页模板文件作为当前内容文档的渲染模板:

/layouts/TYPE/LAYOUT.html
/layouts/SECTION/LAYOUT.html
/layouts/TYPE/single.html
/layouts/SECTION/single.html
/layouts/_default/single.html
/themes/THEME/layouts/TYPE/LAYOUT.html
/themes/THEME/layouts/SECTION/LAYOUT.html
/themes/THEME/layouts/TYPE/single.html
/themes/THEME/layouts/SECTION/single.html
/themes/THEME/layouts/_default/single.html

其中 TYPE 表示内容文档的类型名称,SECTION 表示内容文档的 Section ,THEME 表示主题名称,LAYOUT 表示内容文档指定的模板名。TYPE 和 LAYOUT 可分别通过内容文档头部的 type (默认跟所在 Section 同名)和 layout (默认为单页模板)进行设置 ,SECTION 则由内容文档磁盘路径对应的 Section 决定。

可以看出 Hugo 默认会先从 TYPE 和 SECTION 这些模板目录中查找文档指定的布局 LAYOUT ,再查找相应的单页模板,然后再从网站源默认的布局目录 _default 中查找单页模板,最后会查找当前主题的相关布局目录,可见 Hugo 奉行的准则是:先精确查找,再回退默认。

在单页模板中可以访问网站变量和页面变量以及模板函数,通常我们会将内容文档的内容嵌入到单页模板中,有时也许还想为模板创建一个侧变量用来显示相关信息等,怎样定义单页模板完全取决于自己。

一般情况下,当我们为网站添加过主题之后,主题都会有单页模板的,如果想要覆盖主题中定义的单页模板,可以在网站源的模板目录下面创建相应的单页模板,或者直接创建单页模板 layouts/_default/single.html 作为内容文档未找到单页模板时的默认模板。

内容视图 Hugo 使用内容视图(content views)来以不同于单页模板的方式展示内容文档。比如有时,我们只想要展示文档摘要或者文档列表项而非整个文档,内容视图在此时就特别有用了。

内容视图也是普通的模板文件,Hugo 查找内容视图时会根据当前文档的内容类型进行查找,也就是说同名的内容视图对不同内容类型渲染效果是不同的。Hugo 会依次从以下路径列表中查找可用的内容视图,将找到的第一个模板文件来作为渲染模板

/layouts/TYPE/VIEW.html
/layouts/_default/VIEW.html
/themes/THEME/layouts/TYPE/VIEW.html
/themes/THEME/layouts/_default/view.html

假定我们要为内容类型 post 和 project 分别创建内容视图 li.html ,则对应的模板文件路径为:/layouts/post/li.html 和 /layouts/project/li.html 。如果我们在网站首页使用如下代码罗列所有文档

{{ range .Data.Pages }}
{{ .Render "li"}}
{{ end }}

其中 {{ .Render “li” }} 表示引用当前内容文档对应内容视图 li.html (post 和 project 使用各自的内容视图文件),在内容视图 li.html 中可以访问任何页面变量,下面是 li.html 示例

<li>
<a href="{{ .Permalink }}">{{ .Title }}</a>
<div class="meta">{{ .Date.Format "Mon, Jan 2, 2006" }}</div>
</li>

列表模板 Hugo 使用列表模板(list template)渲染多个被罗列的内容文档,比如:分类标签页面和 Section 页面通常需要罗列逻辑上从属于该类别的所有文档。值得注意的是,不同于单页文档总是被内容文档填充,列表模板一般却不会被内容文档填充(下文会介绍什么情况下列表模板也会填充内容文档)。

Hugo 中列表模板常见的应用场景有:Section 列表页、Taxonomy 列表页、Section RSS 以及 Taxonomy RSS等(注:网站首页虽然也是列表页,可因其特殊性,需要使用特定的模板渲染)。这些页面渲染后的 URL 路径分别如下

Section 列表页

baseURL/SECTION/ ,例如:http://1.com/post/

Taxonomy 列表页

baseURL/PLURAL/TERM/ ,例如:http://1.com/tags/python/

Section RSS

baseURL/SECTION/index.html ,例如:http://1.com/post/index.html

Taxonomy RSS

baseURL/PLURAL/TERM/index.html ,例如:http://1.com/tags/python/

此外,Hugo 会依次从路径列表中查找可用的列表模板,将找到的第一个列表模板文件来作为渲染模板。以上介绍的常见列表页面的查找路径如下

Section 列表

/layouts/section/SECTION.html
/layouts/_default/section.html
/layouts/_default/list.html
/themes/THEME/layouts/section/SECTION.html
/themes/THEME/layouts/_default/section.html
/themes/THEME/layouts/_default/list.html
Taxonomy 列表
/layouts/taxonomy/SINGULAR.html
/layouts/_default/taxonomy.html
/layouts/_default/list.html
/themes/THEME/layouts/taxonomy/SINGULAR.html
/themes/THEME/layouts/_default/taxonomy.html
/themes/THEME/layouts/_default/list.html
Section RSS
/layouts/section/SECTION.rss.xml
/layouts/_default/rss.xml
/themes/THEME/layouts/section/SECTION.rss.xml
/themes/THEME/layouts/_default/rss.xml
Taxonomy RSS
/layouts/taxonomy/SINGULAR.rss.xml
/layouts/_default/rss.xml
/themes/THEME/layouts/taxonomy/SINGULAR.rss.xml
/themes/THEME/layouts/_default/rss.xml

从上面模板的查找路径可以看出,Hugo 首先会查找为特定 SECTION 和 TAXONOMY 定义的模板文件,如果查找失败,会再查找 Section 和 Taxonomy 通用的模板文件,如果还是找不到就使用 layouts/_defaults/list.html 和 layouts/_defaults/rss.xml 。

既然知道了列表模板的用途,也知道了模板文件的查找路径,那么列表模板文件中该写些什么呢?列表文件也是一个普通的模板文件,在模板中可以使用任何 Go 内置模板函数,还可以访问网站模板变量和页面模板变量(用于 Taxonomy 的模板还可以访问代表当前分类的变量 .Data.Singular )。根据列表模板的用途一般来说会在模板中为内容文档创建一个展示列表,此外也许希望对这个内容文档分类或者剔除某些文档,利用简洁而强大的 Go 模板方法可以自定义任何复杂的列表页面。下面是一个用于 Section 的列表模板示例

{{ partial "header.html" . }}
{{ partial "subheader.html" . }}

<section id="main">
  <div>
   <h1 id="title">{{ .Title }}</h1>
        <ul id="list">
            {{ range .Data.Pages }}
                {{ .Render "li"}}
            {{ end }}
        </ul>
  </div>
</section>

{{ partial "footer.html" . }}

分类模板 Hugo 使用分类模板(taxonomy terms template)来渲染当前分类下的所有标签。

要注意同Taxonomy 列表页相区分,Taxonomy 列表页用来罗列属于某个标签下所有的内容文档,优先查找模/layouts/taxonomy/SINGULAR.html 作为该标签列表页的模板,且将页面渲染于 baseURL/PLURAL/TERM/ 。而分类模板页面是用来罗列当前分类下所有标签的,优先查找 /layouts/taxonomy/SINGULAR.terms.html 作为页面模板,且渲染于 baseURL/PLURAL/ 。

Hugo 会依次从路径列表中查找可用的模板,将找到的第一个模板文件来作为渲染模板

/layouts/taxonomy/SINGULAR.terms.html
/layouts/_default/terms.html
/themes/THEME/layouts/taxonomy/SINGULAR.terms.html
/themes/THEME/layouts/_default/terms.html
如果以上模板都不存在,Hugo 就不会渲染分类标签页面。换句话说,分类标签页面的渲染也不一定必须单独使用一个模板文件,我们可以在页面侧边栏之类的地方来渲染分类标签(比如:侧边栏实现一个标签云)。

分类模板中除了可以访问网站变量和页面变量外,还有一些关于分类标签的变量可供我们使用:

.Data.Singular						分类的单数名称,比如:tag
.Data.Plural						分类的复数名称,比如:tags
.Data.Pages							属于当前分类的所有页面
.Data.Terms							属于当前分类的所有标签
.Data.Terms.Alphabetical			属于当前分类的所有标签(字母序)
.Data.Terms.ByCount					属于当前分类的所有标签(根据标签下文档数量排序)
下面是一个示例分类模板,该模板罗列出了当前分类下的所有标签,并给出了标签下所有文档的链接

{{ partial "header.html" . }}
{{ partial "subheader.html" . }}

<section id="main">
  <div>
    <h1 id="title">{{ .Title }}</h1>

    {{ $data := .Data }}
    {{ range $key,$value := .Data.Terms.ByCount }}
    <h2><a href="{{ .Site.LanguagePrefix }}/{{ $data.Plural }}/{{ $value.Name | urlize }}">{{ $value.Name }}</a> {{ $value.Count }}</h2>
    <ul>
    {{ range $value.Pages.ByDate }}
      <li><a href="{{ .Permalink }}">{{ .Title }}</a></li>
    {{ end }}
    </ul>
    {{ end }}
  </div>
</section>

{{ partial “footer.html” . }} 片段模板 Hugo 使用片段模板(partial template)作为其它模板文件的原材料,比如首页模板、单页模板、列表模板等这些模板通常会使用片段模板来创建。这里之所以将片段模板比作原材料,是因为片段模板通常包含了其它模板中的公共部分,反过来说,我们应该将多个模板中的公共内容分离出来创建片段模板文件,然后可以在其它模板中引用该片段文件。使用片段模板的好处在于,不需要重复定义相同的模板内容,而且片段模板十分有利于主题资源的开发,主题中应该将那些想要让用户覆盖的模板内容单独作为一个片段模板,这样主题的使用者只需要定义相同的片段模板就可以对主题片段模板进行替换,片段模板文件是比普通模板文件更加细粒度的模板内容容器。

如何创建片段模板呢?Hugo 默认将模板目录 /layouts/partials/ 及其子目录中的模板文件看作片段模板,片段模板的内容如同普通模板一样可以访问各种模板变量和模板函数,不过片段模板可以访问到的模板变量取决于引用该模板时传入了怎样的变量进来(后面会有讲,如何引用片段模板以及如何传递变量到片段模板)。在网站中最为常见的片段模板也许就是网页头和网页脚,因为网页头和网页脚在网站大多数页面中都是相同的,将其分离于片段模板中是明智的选择,假设我们创建了 /layouts/partials/header.html 和 /layouts/partials/footer.html 片段模板文件,它们的内容分别为

<!DOCTYPE html>
<html class="no-js" lang="en-US" prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb#">
<head>
    <meta charset="utf-8">

    {{ partial "meta.html" . }}

    <base href="{{ .Site.BaseURL }}">
    <title> {{ .Title }} : spf13.com </title>
    <link rel="canonical" href="{{ .Permalink }}">
    {{ if .RSSLink }}<link href="{{ .RSSLink }}" rel="alternate" type="application/rss+xml" title="{{ .Title }}" />{{ end }}

    {{ partial "head_includes.html" . }}
</head>
<body lang="en">
和

<footer>
  <div>
    <p>
    &copy; 2013-14 Steve Francia.
    <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons Attribution">Some rights reserved</a>;
    please attribute properly and link back. Hosted by <a href="http://servergrove.com">ServerGrove</a>.
    </p>
  </div>
</footer>
<script type="text/javascript">

  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-XYSYXYSY-X']);
  _gaq.push(['_trackPageview']);

  (function() {
    var ga = document.createElement('script');
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' :
        'http://www') + '.google-analytics.com/ga.js';
    ga.setAttribute('async', 'true');
    document.documentElement.firstChild.appendChild(ga);
  })();

</script>
</body>
</html>

以上模板内容除了常规的 HTML 代码外,还出现了像 {{ partial “meta.html” . }} 这样的模板语句,这条语句在这里的作用是引用片段模板 meta.html 到当前模板文件中(即 header.html 片段模板文件),就是说 Hugo 允许我们在片段模板中再次引用片段模板。

下面让我们研究一下,如何引用一个片段模板文件,引用片段模板的语法为:{{ partial “path/to/file.html” variables }} ,其中 path/to/file.html 表示被引用的片段模板文件相对于 /layouts/partials/ 目录的路径,比如想要引用 /layouts/partials/post/sidebar.html ,则对应的引用路径为 post/sidebar.html 。其中 variables 表示要传入片段模板的变量(片段模板除了这些传入的变量,是无法访问其它变量的),通常我们会将代表当前模板内所有变量的 . 作为 variables 传入片段模板中。

有没有想过,很多模板引用相同的片段模板文件,在生成网页时,这些片段模板是不是在每个引用模板中都要重新渲染一次呢?有没有办法减少片段模板的渲染次数,毕竟片段模板生成的网页片段除了根据传入变量不同会有改变外,基本的网页结构是相似的。如果想要让 Hugo 提升片段模板的渲染效率(Hugo 会自动缓存已经渲染好的片段模板供后续使用),可以在引用模板文件时用 partialCached 来代替 partial ,并且 Hugo 还支持用户按照类别缓存片段模板,比如: {{ partialCached “footer.html” . .Section }} 的意思是,为每个 Section 渲染一次 footer.html 模板。

模板调试 模板编写中错误在所难免,可以使用模板函数 printf 调试模板变量,下面是几个常见调试样例

{{ printf "%#v" . }}
{{ printf "%#v" $.Site }}
{{ printf "%#v" .Permalink }}
{{ range .Data.Pages }}
    {{/* The context, ".", is now a Page */}}
    {{ printf "%#v" . }}
{{ end }}

Hugo 文档处理

文档排序 当在列表页面展示多篇文档时,就涉及到文档先后顺序的问题了。Hugo 中文档默认是以元信息 weight 来排序,当文档未指定 weight 时,就以元信息 date 来排序,如果这两项都没有指定的话,列表页面看到的文档就是无序的。

不过除了上面 weight 和 date 外,Hugo 还支持我们以更多方式来排序列表页面,我们需要在列表模板文件中使用以下一些模板变量来控制文档的排序

按照元信息权重和日期排序(默认排序方式)

{{ range .Data.Pages }}
<li>
<a href="{{ .Permalink }}">{{ .Title }}</a>
<div class="meta">{{ .Date.Format "Mon, Jan 2, 2006" }}</div>
</li>
{{ end }}
按照元信息日期排序

{{ range .Data.Pages.ByDate }}
<!-- ... -->
{{ end }}
按照发布日期排序

{{ range .Data.Pages.ByPublishDate }}
<!-- ... -->
{{ end }}
按照失效日期排序

{{ range .Data.Pages.ByExpiryDate }}
<!-- ... -->
{{ end }}
按照修改日期排序

{{ range .Data.Pages.ByLastmod }}
<!-- ... -->
{{ end }}
按照文档内容长度排序

{{ range .Data.Pages.ByLength }}
<!-- ... -->
{{ end }}
按照文档标题排序

{{ range .Data.Pages.ByTitle }}
<!-- ... -->
{{ end }}
按照链接标题排序

{{ range .Data.Pages.ByLinkTitle }}
<!-- ... -->
{{ end }}
按照其它元信息排序

{{ range (.Date.Pages.ByParam "author.last_name") }}
<!-- ... -->
{{ end }}
反转排序(以上所有排序都可反转)

{{ range .Data.Pages.ByTitle.Reverse }}
<!-- ... -->
{{ end }}
除此之外,文档还可以按照分类进行排序,而分类标签本身可以按照标签字母序来排序

<ul>
{{ $data := .Data }}
{{ range $key, $value := .Data.Taxonomy.Alphabetical }}
<li><a href="{{ .Site.LanguagePrefix }}/{{ $data.Plural }}/{{ $value.Name | urlize }}"> {{ $value.Name }} </a> {{ $value.Count }} </li>
{{ end }}
</ul>

或者按照关联到该分类标签的文档数量排序(即按照分类的热门程度排序)

<ul>
{{ $data := .Data }}
{{ range $key, $value := .Data.Taxonomy.ByCount }}
<li><a href="{{ .Site.LanguagePrefix }}/{{ $data.Plural }}/{{ $value.Name | urlize }}"> {{ $value.Name }} </a> {{ $value.Count }} </li>
{{ end }}
</ul>

属于某个分类的文档默认按照 weight 和 date 来排序,并且支持为文档指定分类排序时的权重,这样可以调整文档在分类中的顺序,这个功能通过文档中指定元数据 taxonomyname_weight 来实现,其中 taxonomyname 代表分类名。

文档分组 当在列表页面展示多篇文档时,Hugo 支持我们根据文档类型、日期或者 Section 来分组显示文档。

按照 Section 分组

{{ range .Data.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 }}
按照日期分组

{{ range .Data.Pages.GroupByDate "2006-01" }}
<!-- ... -->
{{ end }}
按照发布日期分组

{{ range .Data.Pages.GroupByPublishDate "2006-01" }}
<!-- ... -->
{{ end }}
按照其它元信息分组

{{ range .Data.Pages.GroupByParam "param_key" }}
<!-- ... -->
{{ end }}
反转分组排序

{{ range (.Data.Pages.GroupByDate "2006-01").Reverse }}
<!-- 利用模板函数Reverse来反转 -->
{{ end }}

{{ range .Data.Pages.GroupByDate "2006-01" "desc" }}
<!-- 或者直接指定排序方向 -->
{{ end }}
组内文档排序

{{ range .Data.Pages.GroupByDate "2006-01" "asc" }}
<h3>{{ .Key }}</h3>
<ul>
  {{ range .Pages.ByTitle }}
  <!-- 可以按照之前介绍排序文档的各种方法来排序组内文档 -->
  {{ end }}
</ul>
{{ end }}
文档过滤
有时候也许想要排除某些文档在列表页面显示,Hugo 支持我们在列表页面限制文档显示数量以及限制显示的文档种类。

限制文档显示数量

{{ range first 10 .Data.Pages }}
  <!-- 利用模板函数first,只显示排在前面的10篇文档 -->
  {{ .Render "summary" }}
{{ end }}
根据条件过滤某些文档

{{ range where .Data.Pages "Section" "post" }}
 <!-- 利用模板函数where,只筛选显示Section为post的文档 -->
 {{ .Content }}
{{ end }}

{{ range first 5 (where .Data.Pages "Section" "post") }}
 <!-- 同时使用where和first -->
 {{ .Content }}
{{ end }}

文档摘要 Hugo 默认会截取文档前70个词作为文档摘要,并将摘要内容存放在模板页面变量 .Summary ,同时提供模板变量 .Truncated 来记录截取的摘要是否包含了文档的全部内容。同时 Hugo 还支持我们在内容文档中明确指定将哪些内容作为该文档的摘要,具体来说需要在文档中插入一行 来标识位于该行之前的内容作为摘要,同理 Hugo 会将摘要存放在模板页面变量 .Summary ,并用模板变量 .Truncated 标识摘要是否包含了文档全部内容。

利用文档的摘要功能可以实现“阅读更多…”这样的功能,示例如下

{{ range first 10 .Data.Pages }}
  <div class="summary">
    <h4><a href="{{ .RelPermalink }}">{{ .Title }}</a></h4>
    {{ .Summary }}
  </div>
  {{ if .Truncated }}
  <div class="read-more-link">
    <a href="{{ .RelPermalink }}">Read More…</a>
  </div>
  {{ end }}
{{ end }}

参考文档