Hugo的布局

了解Layouts是如何帮助您设置和重复使用 Hugo 网站的主要结构。

善于利用Layouts是创建一个易于维护、灵活和繁荣的Hugo网站的关键,因此,让我们看看它们是如何发挥作用的。

什么是布局(Layouts)?

Layouts 是用于页面上围绕内容的所有 “框架”。例如,想想一个网站上不同页面之间保持相对不变的所有东西,如页眉和页脚。

在Hugo中,网站上的每个页面都是一个内容文件。最上面是一小段元数据,叫做front matter,后面是markdown。内容文件的目标是,以其最纯粹的形式存储内容。在内容文件中很少有任何HTML或其他表现逻辑。

所有用于显示和格式化内容的花哨的HTML都存在于布局中。一个布局可能被用于多个内容页面。例如,在一个项目组合网站上,你可能会对你的“关于”页和服务页面使用相同的布局。其他时候,特别是如果你正在做一些复杂的事情,你可能会有一个布局专门用于一个单独的内容页面,例如一个带有复杂表格的联系页面。

你的第一页 Your first page

Hugo有一个叫做 Page Bundles 的概念,可能很难让你头脑清醒。我们将在这里做最低限度的工作,以使您的网站正常运行。

默认情况下,内容页使用一种叫做 single(单页)的布局。创建一个确切命名为_index.md的内容文件,包括下划线和所有内容,这是一个特例。(Creating a content file which is named exactly , underscore and all, is the exception to this. )

这些页面就像它周围页面的目录,默认使用一种叫做 list(列表)的布局。

就像一本好的非小说类书籍一样,从目录开始是一个很好的做法。您将为网站的主页执行此操作。

如果这一切听起来令人困惑,那么它实际上比听起来简单得多。在\content目录下创建一个名为_index.md的文件:hugo new content\_index.md

---
title: Home
---
Hello, I'm a ferocious lion.

--- 是之前提到的元数据或 FrontMatter。我们将在今后的课程中讨论这个问题。

您的第一个布局 Your first layout

Hugo 有一个层次结构来确定用于content_d文件的布局。最后的退路是在 _default 目录中寻找一个布局。(The last fallback is looking for a layout in the directory.)让我们为主页创建默认列表布局。

/layouts/目录中创建一个新的文件夹,名为 _default,在里面创建 list.html,内容如下:

<!Doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>{{ .Page.Title }}</title>
</head>

<body>
  {{ .Content }}
</body>
</html>

这些 {{ *** }} 部分是使用Hugo模板从我们的内容文件(_index.md)中渲染内容,我们很快会介绍。

好了, 这是在你的Hugo网站上通过布局呈现的真实内容。让我们用你网站上的第二个页面来庆祝一下。

您的第二个布局

让我们在你的网站上弄第二个页面。创建:/content/about.md(注意,我们在这里没有使用下划线)

---
title: About
---
I'm learning Hugo, one step at a time.

about.md默认会寻找一个名为single的布局。

我们可以把/layouts/_default/list.html克隆到/layouts/_default/single.html中,这样就可以正常工作了,但你将不得不维护同一个布局的两个非常相似的版本。我们希望减少重复,并尽可能地使我们的生活更轻松。

相反,在/layouts/_default/baseof.html创建一个新的布局,内容如下:

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>{{ .Page.Title }}</title>
</head>

<body>
  {{ block "main" . }}
  {{ end }}
</body>
</html>

这与list布局几乎相同,只有一个小的变化。我们现在没有输出{{ .Content }},而是有一个主 “块”。这个块是一个占位符,其他布局可以用它来只指定页面的那一部分。

让我们用你的列表布局来做这件事。将/layouts/_default/list.html里的内容替换为:

{{ define "main" }}
  {{ .Content }}
{{ end }}

用完全相同的代码创建/layouts/_default/single.html

试着进入http://localhost:1313/about/,看看你的新的关于页面。

添加样式

这是一门Hugo课程,所以我们不打算进入任何CSS。但是,您和我都同意默认浏览器样式还有很多不足之处。我不仅会给你一个样式表,它还会是一个 Sass 样式表,以便在您扩展网站时易于维护。

我们已经有一个叫做static的目录,所有的静态资源都在这里。如果我们使用一个普通的CSS文件,这将是放置它的地方。我们使用的是Sass,它需要被处理成一个CSS文件。

在我们网站的根部创建一个名为 /assets/ 的目录。这是需要某种形式的处理的资源将住在这里。在assets中,创建一个名为sass的目录。这是我们的.scss文件的位置。让我们在/assets/sass/main.scss创建一个主文件,内容如下:

/* 设置 body 元素的宽度、外边距和字体 */
body {
  width: 400px; /* 设置宽度为 400 像素 */
  margin: 0 auto; /* 上下外边距为 0,左右外边距为自动,居中对齐 */
  font-family: sans-serif; /* 设置字体为无衬线字体 */
}

/* 设置导航栏中的 ul 元素的样式 */
nav ul {
  list-style: none; /* 移除列表项的默认样式 */
  padding: 3px 5px; /* 设置内边距,上下 3 像素,左右 5 像素 */
  background: #111; /* 设置背景颜色为深灰色 */

  /* 设置导航栏中的 a 元素的样式 */
  a {
    color: #fff; /* 设置文字颜色为白色 */
    text-decoration: none; /* 移除下划线 */

    /* 设置鼠标悬停时的样式 */
    &:hover {
      text-decoration: underline; /* 添加下划线 */
    }
  }

  /* 设置导航栏中的 li 元素的样式 */
  li {
    display: inline; /* 设置为行内元素 */
  }
}

/* 设置 footer 元素的样式 */
footer {
  background: #f2f2f2; /* 设置背景颜色为浅灰色 */
  padding: 2px 2px; /* 设置内边距,上下左右均为 2 像素 */
  font-size: .7em; /* 设置字体大小为 0.7 倍的默认字体大小 */
  text-align: center; /* 设置文字居中对齐 */
}

/* 设置 id 为 map 的元素的高度 */
#map {
  height: 300px; /* 设置高度为 300 像素 */
}

我们还需要在我们的HTML中引用样式表。打开/layouts/_default/baseof.html,在</title>下面插入以下内容:

{{ $style := resources.Get "sass/main.scss" | resources.ToCSS | resources.Minify }}
<link rel="stylesheet" href="{{ $style.Permalink }}">

这告诉Hugo建立.scss文件并在一个样式表标签中输出链接。

你会注意到这将适用于 listsingle 布局。我们之前所做的工作已经派上用场了! 你可能需要重新启动你的 hugo serve 以便在你的浏览器中看到这些变化。

下一步是什么?

目前,访问 “关于” 页面的唯一方法是直接导航到它。在下一课,我们将使用Hugo局部添加网站导航。

Hugo 的局部模板 Hugo partials

在这个Hugo教程中,学习如何用Partials将你的Hugo页面分解成更小的 “组件”。

在本课中,我们将在网站的顶部创建一个导航栏,这个导航栏对所有页面都是一样的,我们将使用一个局部来实现。

Partial的想法很简单:它是一个可以包含在布局中的文件,以减少重复或简单地隐藏一些复杂性。你将用一个局部模板来为你的网站添加一个导航条。虽然你可以把这个逻辑直接添加到你的 baseof.html 中,但有时把一个布局分割成较小的局部是很好的,这样你就不需要处理一个2000行的文件了。

你的第一个Partial

在你的layouts目录下创建一个partials文件夹,所以最终路径将是/layouts/partials/。在里面创建nav.html,内容如下:

<nav>
  <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/about/">About</a></li>
  </ul>
</nav>

这就是已经处理好的导航。现在是时候把它纳入你的布局中了。打开/layouts/_default/baseof.html,在<body>下面添加以下内容:

{{ partial "nav.html" }}

在Go中,你的单引号对字符串没有好处,只有双引号才能指定一个字符串。

渲染你的页面,就可以了。

你的第二个Partial

让我们尝试另一个场景来展示Partial的力量。

我们将进一步简化 baseof.html,把 <head> 的内容移到一个Partial。创建 /layouts/partials/meta.html,内容如下:

<meta charset="utf-8">
<title>{{ print .Page.Title }}</title>
{{ $style := resources.Get "sass/main.scss" | resources.ToCSS | resources.Minify }}
<link rel="stylesheet" href="{{ $style.Permalink }}">

在这个部分,我们有一些变量需要当前页面的上下文。幸运的是,传递当前页面的上下文可以用一个字符来完成。

打开/layouts/_default/baseof.html,用以下内容替换<head>的内容。

{{ partial "meta.html" . }}

结尾处的那个小 . 是传递当前页面的上下文,它允许部分打印出当前页面的标题。你会经常在你的Hugo网站中看到这种情况。

hugo模板的基础知识

模板化

Go模板是Hugo中使用的灵活的模板语言。在这一课中,我们将了解Go模板的基本概念,并看看你如何在你的网站上使用它。

通过Hugo模板,你可以控制你的页面是如何呈现的。你可以使用变量,在数组上循环,检查条件,并运行函数。把它看作是一种简单的编程语言,以帮助建立你网站上的页面。你布局中的那些大括号{{ }},就是Hugo模板。

什么是front matter

Font matter 是你的内容文件顶部的一个元数据片段。有些元数据将是专门为Hugo而设的。例如,设置一个布局,或表明当前页面是一个草稿。其他形式的元数据将是针对你的网站的,例如,表明在页面上使用哪种类型的英雄,或你最喜欢的五种食物的列表。

Front matter 的内容是以一个小的YAML片段的形式出现在内容文件的顶部。我们在索引页和关于页上都看到过这种形式。

它可能看起来不大,但我们可以在我们的布局中使用Hugo的模板来引用这个front matter。

什么是Hugo模板化?

Hugo使用Go模板作为其布局中的模板语言。一旦你掌握了它,它就很容易了。与Hugo中的许多事情一样,有时展示比讲述更容易。

Hugo模板化的例子

输出一个字符串

<!-- Go模板是一个普通的HTML页面。当你想执行一段代码时,你可以使用双大括号,像这样:-->
<p>A Go template is a normal HTML page. When you want to execute a piece of code, you can use double curly braces like this: {{ "Hello!" }}.</p>

Front matter的输出

<!-- You can reference a variable from your front matter in a layout with .Params. For example, you could output the title on your pages with: 
你可以在布局中用.Params来引用你Front matter的一个变量。例如,你可以在你的页面上输出标题用:-->

<title>{{ .Params.title }}</title>

站点配置的输出

<!-- Sometimes you'll want to set a variable globally in your config.toml. 
Hugo has already initalized a title in your config.toml. You can access
a variable from your global config with site. For example: 
有时你会想在config.toml中设置一个全局变量。
Hugo已经在你的config.toml中设置了一个标题。你可以从你的全局配置中用site.访问一个变量。比如说:
-->

<title>{{ .Params.title }} | {{ .Site.title }}</title>

条件

<!-- We might want to check if the front matter title exists. If
it exists, output it; if not, use the global config title.
我们可能想检查一下前面的标题是否存在。如果它存在,就输出它;如果不存在,就使用全局配置的标题。-->

{{ if isset .Params "title" }}
  <title>{{ .Params.title }}</title>
{{ else }}
  <title>{{ .Site.title }}</title>
{{ end }}

设置并输出一个变量

<!-- variables at set with a $ sign. For example: 
变量在设置时带有$符号。例如:-->
{{ $favorite_food := "Gazelle" }}
{{ $favorite_food }}

循环

<!--In Go, an array that can change size is called a slice.
You can iterate over an array or slice using range. 
在Go中,一个可以改变大小的数组被称为切片。
你可以用range来遍历一个数组或切片。-->
{{ $best_friends := slice "pumbaa" "timon" "nala" "rafiki" }}

<ul>
{{ range $best_friends }}
  <li>{{ . }}</li>
{{ end }}
</ul>

Nested Key values 嵌套键值

内容文件:

---
title: Appearance
apperance:
  eyes: green
  snoot: boopable
  whiskers: true
  limbs:
    - claws: 5
      side: left
      position: front
    - claws: 4
      side: right
      position: front
    - claws: 3
      side: left
      position: back
    - claws: 5
      side: right
      position: back
---

Layouts file:

<!-- If we want to output all of these variables, we could call .Params.appearance.x for each one. Instead we could use `with` to change the context to '.'. It also has the benefit of checking whether the variable exists and won't run the block if it doesn't. 
如果我们想输出所有这些变量,我们可以为每个变量调用.Params.appearance.x。相反,我们可以使用`with`来改变上下文为'.'。它还有一个好处,就是检查变量是否存在,如果不存在,就不会运行这个块。
-->

{{ with .Params.appearance }}
<h3>My top appearance traits</h3>
<dl>
 <dt>Eyes</dt>
  <dd>{{ .eyes }}</dd>

  <dt>Snoot</dt>
  <dd>{{ .snoot }}</dd>

  <dt>Whiskers</dt>
  <dd>{{ .whiskers }}</dd>
 
 {{ with .limbs }}
    <dt>Claws</dt>
    <dd>
     <ul>
      {{ range . }}
        <li>{{ .position }} {{ .side }} {{ .claws }</li>
      {{ end }}
      </ul>
    </dd>
  {{ end }}
</dl>
{{ end }}

这些是模板化的基础。在你的Hugo之旅中,你将会使用所有这些概念。你可以浏览一下模板文档,了解一下你还能做什么。

如果你喜欢保持HTML输出的整洁,你可以使用{{-</code>和<code>-}}来修剪标签周围的空白。Hugo文档中就有一个很好的例子

把它全部放在一起

让我们把我们新的Hugo模板知识付诸行动,为你的网站添加一个包括你的名字和当前年份的页脚。在此基础上,我们将添加一个可选的前台事项字段,你可以用它来隐藏特定页面上的页脚。

让我们从一个简单的问题开始。你的名字。在你的config.toml中把它作为一个新的键加入。因为这只是这个网站的东西,而不是一个特殊的Hugo术语,我们把它放在params对象下:

[params]
name = 'Simba'

现在让我们来创建局部。将footer.html添加到你的layout partials目录,内容如下:

{{ with .Params.hide_footer }}
  <!-- No footer here! 这里没有页脚! -->
{{ else }}
  <footer>
    Website made by {{ .Site.Params.name }} in {{ now.Year }}
  </footer>
{{ end }}

最后在baseof.html布局中的</body>之前调用局部:

{{ partial "footer.html" . }}

为了检查hide_footer前台的工作情况,让我们通过在前台添加这个来关闭about.md页面上的页脚:

hide_footer: true

运行hugo service,在浏览器中查看你的网站。主页有页脚,而 “关于 “页却没有。

在Hugo写博客 Blogging in Hugo

在这个Hugo教程中,你将学习如何用Hugo的内容和布局创建一个博客。

Hugo中的博客相对来说是比较简单的。它包括一个列出所有博客文章的页面,以及一系列带有文章日期的内容页面。这就是它的全部内容。

创建一个博客列表页

你将在这个例子中看到布局分层是如何工作的。

在你的/content/目录下创建一个名为post的目录,并在其中创建一个名为_index.md的文件,内容如下:

title: Blog

_index.md - 还记得这意味着什么吗?它是一个内容文件,在这种情况下,它将列出你的文章。目前,它将尝试使用/layouts/_default/list.html布局。你不希望使用这个布局,因为这个页面没有任何内容。相反,你要创建一个新的布局,专门用于列出文章。

Hugo布局层次的工作方式是,它将首先寻找一个与当前章节相匹配的布局(将章节视为目录),然后再回到_default中的全局默认值。换句话说,我们可以在layouts目录下创建一个名为post的新目录,并在里面创建list.html,内容如下:

{{ define "main" }}
  <h1>My posts</h1>
  <ul>
  {{ range .Pages }}
    <li>
      <a href="{{ .Permalink }}">{{ .Title }}</a> - {{ .Date.Format "January 2, 2006" }}
    </li>
  {{ end }}
  </ul>
{{ end }}

这里有一些新的概念;让我解释一下。

一个列表页(_index.html)有一个包含其所有子页的数组,变量为.Pages

.Date的格式被调用,并被传递一个2006年的随机日期。为什么会这样呢?这是 Go 的一个格式化日期的怪癖。你可以在这里阅读更多关于它的信息。

.Permalink可以在任何页面上调用,以获得其末端的 URL。如果你想链接到一个页面,它就特别有用。

这就是我们的列表页所需要的一切。让我们转到一篇文章上。

创建一个帖子 我们已经完成了最困难的部分。让我们通过创建一些帖子来完成这个博客。

帖子住在/content/posts目录下,不需要任何特殊的命名规则。我喜欢鼓励的一个建议是在文件名中加入文章的日期。Hugo会完全忽略它,但当你在成百上千的文章中找到一篇文章时,它是有帮助的。

让我们创建三篇博文,让你开始吧。

/contents/posts/2022-04-03-i-like-to-roar.md

---
title: I like to roar
date: 2022-04-03
---
Hi, quick update from me. I just want to let everyone know that I like roaring.

/contents/posts/2022-04-02-today-i-made-friends.md

---
title: Today I made friends
date: 2022-04-02
---
I got lost in the woods today. Two friends, a Meerkat and a Warthog, found me,sung a song with me and gave my life new meaning. Today was a good day.

/contents/posts/2022-04-01-vegan-experiment.md

---
title: Vegan experiment
date: 2022-04-01
---
I tried to become a vegan today. I made it to lunch time and couldn't bear the sight of another green leaf. Yuck!

就像博客列表页一样,这些帖子将尝试使用/layouts/_default/single.html。让我们在/layouts/posts/single.html创建一个专门用于文章的布局,内容如下:

{{ define "main" }}
  <h1>{{ .Params.Title }}</h1>
  <p>{{ .Date.Format "January 2, 2006" }}</p>
  {{ .Content }}
{{ end }}

最后,让我们把博客添加到导航中。打开/layouts/partials/nav.html,添加另一个项目:

<li><a href="/posts/">Blog</a></li>

运行hugo serve,在浏览器中打开网站,查看你的杰作。

Using Data with Hugo

使用数据文件导入现有数据,或者在这个Hugo教程中拥有管理全局数据的简单方法。

Hugo中的数据文件有点像一个只读的数据库。它的工作方式是你可以把JSON、CSV、YAML、XML或TOML文件放在一个叫做data的目录中,然后用.Site.Data在布局中访问这些数据。如果你想花哨一点,你也可以在构建时从外部来源下载JSON或CSV文件。

创建你的第一个数据文件

首先,我们要创建一个包含所有你喜欢的度假地点的数据文件。创建/data/vacation_spots.yml,内容如下:

- name: Masai Mara National Reserve
  latitude: -1.484751
  longitude: 35.101904
- name: Serengeti National Park
  latitude: -2.333333
  longitude: 34.833332
- name: Okavango Delta
  latitude: -19.304543
  longitude: 22.643703
- name: Etosha National Park
  latitude: -18.855591
  longitude: 16.32932
- name: Kidepo Valley Park
  latitude: 3.882778
  longitude: 33.874444

什么是短码?

为了列出你最喜欢的度假地点,你需要:

  • 地图软件–你将使用神奇的OpenStreetMapLeaflet
  • 一个标记物的JavaScript列表–你目前在一个数据文件中拥有这个列表
  • 用于设置地图和标记的JavaScript
  • 一个容纳地图的div

我们可以把所有这些放在一个内容文件中,但这将比我们目前拥有的简单的markdown要复杂得多。如果我们想在另一个页面上再次使用这个地图呢?那就需要复制和粘贴大量的HTML了。

幸运的是,Hugo有一个答案,它叫做Shortcodes(短码)。短码类似于部分代码,只是你在内容文件中使用它们。Hugo甚至已经内置了一堆

你将创建一个短码,可以在任何内容文件中使用,以创建一个带有标记位置的地图。让我们看看它是如何工作的。

你的第一个短码

/layouts/中创建一个名为shortcodes的目录,并添加一个名为 vacation_spots.html的文件,内容如下:

<div id="map">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" crossorigin=""></script>
<script>
  let markers = {{ $.Site.Data.vacation_spots }};
</script>
<script src="/map.js"></script>
</div>

这里有几件事情,让我解释一下:

首先,我们有一个地图元素,它将容纳地图。

然后我们加载Leaflet的样式表,它将帮助我们在地图上创建漂亮的图钉和弹出式窗口。

接下来是Leaflet的JavaScript文件。

接下来是来自你的数据文件的标记物列表。这里它被输出为一个JSON数组。

最后我们参考/map.js,我们很快就会创建。它负责初始化地图和添加标记。

让我们添加JavaScript来初始化地图和添加标记。创建/static/map.js,内容如下:

const map = L.map('map');

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', 
    {attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'})
    .addTo(map);

let bounds = [];
for (let i = 0; i < markers.length; i++ ) {
    const marker = L.marker([markers[i].latitude, markers[i].longitude]).addTo(map);
    marker.bindPopup(markers[i].name);
    bounds.push([markers[i].latitude, markers[i].longitude]);
}

map.fitBounds(bounds);

我们不是来学习JavaScript的,所以我把这个留给你来解读(或不解读)。

下一步是什么?

这只是你Hugo之旅的开始。你现在已经掌握了建立一个基本的Hugo网站的技能。要继续这个旅程,我推荐一些资源:

Hugo文档是学习Hugo所提供的一切的伟大资源。

Hugo社区论坛对新来者很欢迎。这是一个获得支持和与社区联系的好地方。

Bootstrap到客户可编辑的Hugo网站是一个更高级的Hugo教程,它告诉你如何将一个纯粹的静态网站转换成客户可更新的Hugo网站。

谢谢你的阅读,祝你Hugo快乐

参考链接:

https://cloudcannon.com/community/learn/hugo-beginner-tutorial