A note from the authors: Some of the information and instructions in this book are now out of date because of changes to Hugo and the blogdown package. If you have suggestions for improving this book, please file an issue in our GitHub repository. Thanks for your patience while we work to update the book, and please stay tuned for the revised version!

In the meantime, you can find an introduction to the changes and new features in the v1.0 release blog post and this "Up & running with blogdown in 2021" blog post.

— Yihui, Amber, & Alison

2.5 Templates

Hugo 主题由两个主要组件组成:templates 和 web assets。前者是必不可少的,它告诉 Hugo 如何渲染页面。7 后者是可选的,但也很重要。它通常由 CSS 和 JavaScript 文件以及图像和视频等其他资源组成。这些资源决定了您网站的外观和功能,有些资源可能嵌入到您的网页内容中。

您可以从官方文档(https://gohugo.io/templates/overview/)了解有关 Hugo 模板的更多信息。有很多不同类型的模板。为了让你更容易掌握关键思想,我创建了一个非常小的 Hugo 主题,它涵盖了普通用户可能需要的大部分功能,但总行数只有 150 行左右,所以我们可以在以下小节中谈论该主题的所有源代码。

2.5.1 A minimal example

XMin 是一个 Hugo 主题,我花了大约 12 个小时从头开始编写。大约花了半个小时在模板上,花了 3.5 个小时在调整 CSS 样式上,花了 8 个小时在文档上(https://xmin.yihui.org)。我想这可能是一个代表性的案例,说明你在设计主题时会在每个部分花费多少时间。也许我们的本性是花更多的时间在 CSS 之类的装饰性内容上,而不是模板之类的基本内容上。同时,编码通常比文档更容易。

我们将展示 XMin 主题的源代码。由于主题将来可能会偶尔更新,因此您可以通过以下链接获取我们将在本节中讨论的固定版本:https://github.com/yihui/hugo-xmin/tree/4bb305。下面是主题中所有文件和目录的树形视图

hugo-xmin/
├── LICENSE.md
├── README.md
├── archetypes
   └── default.md
├── layouts
   ├── 404.html
   ├── _default
   │   ├── list.html
   │   ├── single.html
   │   └── terms.html
   └── partials
       ├── foot_custom.html
       ├── footer.html
       ├── head_custom.html
       └── header.html
├── static
   └── css
       ├── fonts.css
       └── style.css
└── exampleSite
    ├── config.toml
    ├── content
       ├── _index.md
       ├── about.md
       ├── note
       │   ├── 2017-06-13-a-quick-note.md
       │   └── 2017-06-14-another-note.md
       └── post
           ├── 2015-07-23-lorem-ipsum.md
           └── 2016-02-14-hello-markdown.md
    ├── layouts
       └── partials
           └── foot_custom.html
    └── public
        └── ...

LICENSE.mdREADME.md 不是主题的必需组件,但您绝对应该为源代码选择许可证,以便其他人可以正确使用您的代码,并且 README 可以是您的软件的简短文档。

文件 archetypes/default.md 定义了用户可以创建新帖子的默认模板。在此主题中,default.md 仅提供空的 YAML metadata:

---
---

主题中最重要的目录是 layouts/static/。HTML 模板存储在 layouts/ 下,assets 存储在 static/ 下。

要理解 layouts/,你必须了解一些关于 HTML 的基础知识(参见 Section B.1),因为该目录下的模板大多是 HTML 文档或片段。在 layouts/ 下有很多可能的子目录类型,但我们这里只介绍两种:_default/partials/

  • _default/ 目录是放置网页默认模板的位置。在 XMin 主题中,我们有三个模板:single.htmllist.htmlterms.html

    • single.html 是用于渲染单个页面的模板。单个页面基本上对应于 content/ 下的 Markdown 文档,它包含 (YAML) metadata 和 content。通常我们想要呈现页面标题、作者、日期和内容。下面是 XMin 的 single.html 的源代码:

      {{ partial "header.html" . }}
      <div class="article-meta">
      <h1><span class="title">{{ .Title }}</span></h1>
      {{ with .Params.author }}
      <h2 class="author">{{ . }}</h2>
      {{ end }}
      {{ if .Params.date }}
      <h2 class="date">{{ .Date.Format "2006/01/02" }}</h2>
      {{ end }}
      </div>
      
      <main>
      {{ .Content }}
      </main>
      
      {{ partial "footer.html" . }}

      您会看到很多对双花括号 {{}},这就是您使用 Hugo 的变量和函数对模板进行编程的方式。

      该模板以部分模板 header.html 开头,您很快就会看到其源代码。现在,您可以将其想象为页面 body 之前的所有 HTML 标记(例如 <html><head>)。部分模板主要是为了重用 HTML 代码。例如,所有 HTML 页面可能共享非常相似的 <head></head> 标记,您可以将公共部分分解为部分模板。

      页面的 metadata 包含在带有 article-meta 类的 <div> 元素中。我们建议您在设计模板时为 HTML 元素分配 classes,以便更轻松地使用 class names 将 CSS 样式应用到这些元素。在模板中,您可以访问 Hugo 提供的许多变量,例如 .Title 变量存储页面标题的值,我们将标题写在第一级标题 <h1> 中的 <span> 中。同样,作者和日期也写在 <h2> 中,但前提是它们在 YAML metadata 中提供。语法 {{ with FOO }}{{ . }}{{ end }}{{if FOO }}{{ FOO }}{{ end }} 的简写,即,使用 {{ . }} 可以省去两次键入表达式 FOO 的麻烦。方法 .Format 可以应用于日期对象,在本主题中,我们将日期格式化为 YYYY/mm/dd 形式(2006/01/02 是 Go 中指定格式的方式)。

      然后我们显示页面的内容,该内容存储在变量 .Content 中。内容包含在语义 HTML 标记 <main> 中。

      在我们添加另一个部分模板 footer.html (源代码很快就会显示)后,模板就完成了。

      为了更容易理解模板的工作原理,我们在下面展示了一个最小的示例文章:

      ---
      title: Hello World
      author: Frida Gomam
      date: 2017-06-19
      ---
      
      A single paragraph.

      使用模板 single.html,它将转换为 HTML 页面,其源代码看起来或多或少像这样(省略了页眉和页脚):

      <div class="article-meta">
        <h1><span class="title">Hello World</span></h1>
        <h2 class="author">Frida Gomam</h2>
        <h2 class="date">2017/06/19</h2>
      </div>
      
      <main>
        <p>A single paragraph.</p>
      </main>

      有关单个页面的完整示例,您可以查看 https://xmin.yihui.org/about/

    • list.html 是用于呈现页面列表的模板,例如博客文章列表或类别或标签内的页面列表。这是它的源代码:

      {{ partial "header.html" . }}
      
      {{if not .IsHome }}
      <h1>{{ .Title }}</h1>
      {{ end }}
      
      {{ .Content }}
      
      <ul>
        {{ range (where .Data.Pages "Section" "!=" "") }}
        <li>
          <span class="date">{{ .Date.Format "2006/01/02" }}</span>
          <a href="{{ .URL }}">{{ .Title }}</a>
        </li>
        {{ end }}
      </ul>
      
      {{ partial "footer.html" . }}

      同样,它使用两个部分模板 header.htmlfooter.html。表达式 {{if not .IsHome }} 的意思是,如果该列表不是主页,则显示页面标题。这是因为我不想在主页上显示标题。这只是我个人的喜好。如果您愿意,您当然可以在主页的 <h1> 中显示标题。

      {{ .Content }} 显示列表的内容。请注意,通常 .Content 为空,这可能会令人惊讶。这是因为默认情况下不会从源 Markdown 文件生成列表页面。然而,有一个例外。当你在列表名对应的目录下编写一个特殊的 Markdown 文件 _index.md 时,列表的 .Content 将是这个 Markdown 文件的内容。例如,您可以在 content/_index.md 中定义您的主页内容,在 content/post/_index.md 中定义帖子列表页面的内容。

      接下来,我们使用循环(range)生成列表,该列表通过按页面部分不应为空的条件过滤的所有页面。Hugo 中的 “Section” 是指 content/ 下的一级子目录名称。例如,content/post/foo.md 的部分是 post。因此,过滤器意味着我们将列出 content/ 子目录下的所有页面。这将排除根 content/ 目录下的页面,例如 content/about.md

      请注意,变量 .Data 是动态的,其值根据您要生成的特定列表而变化。例如,列表页面 https://xmin.yihui.org/post/ 仅包含 content/post/ 下的页面,https://xmin.yihui.org/note/ 仅包含 content/note/ 下的页面。这些列表页面是由 Hugo 自动生成的,您不需要显式循环浏览 postnote 部分。也就是说,单个模板 list.html 将根据您网站上的部分和分类术语(例如类别和标签)生成多个页面列表。

      列表项由 <ul> 中的 HTML 标记 <li> 表示。每个项目由日期、链接和页面标题组成。您可以查看 https://xmin.yihui.org/post/ 以获取列表页面的完整示例。

    • terms.html 是分类术语主页的模板。例如,您可以使用它来生成类别或标签的完整列表。源代码如下:

      {{ partial "header.html" . }}
      
      <h1>{{ .Title }}</h1>
      
      <ul class="terms">
        {{ range $key, $value := .Data.Terms }}
        <li>
          <a href='{{ (print "/" $.Data.Plural "/" $key) | relURL }}'>
            {{ $key }}
          </a>
          ({{ len $value }})
        </li>
        {{ end }}
      </ul>
      
      {{ partial "footer.html" . }}

      list.html 类似,它也使用循环。变量 .Data.Terms 存储分类法下的所有术语,例如所有类别名称。您可以将其视为 R 中的命名列表(在 Go 中称为 map),其中名称是术语,值是页面列表。变量 $key 表示术语,$value 表示与该术语关联的页面列表。我们在每个 <li> 中渲染的是术语页面的链接以及使用该术语的帖子数(len 是一个返回对象长度的 Go 函数)。

      Hugo 自动渲染所有分类页面,路径名称是分类的复数形式,例如 https://xmin.yihui.org/categories/https://xmin.yihui.org/tags/。这就是 .Data.Plural 的含义。前导 $ 是必需的,因为我们位于循环内部,并且需要从外部范围访问变量。该术语的链接通过管道 | 传递给 Hugo 函数 relURL 使其相对,这是一个很好的做法,因为相对链接更可移植(独立于域名)。

  • partials/ 目录是放置 HTML 片段的地方,以便其他模板通过 partial 函数重用。该目录下有四个部分模板:

    • header.html 主要定义 <head> 标签和 <nav> 标签中的导航菜单。

      <!DOCTYPE html>
      <html lang="{{ .Site.LanguageCode }}">
        <head>
          <meta charset="utf-8">
          <title>{{ .Title }} | {{ .Site.Title }}</title>
          <link href='{{ "/css/style.css" | relURL }}'
            rel="stylesheet" />
          <link href='{{ "/css/fonts.css" | relURL }}'
            rel="stylesheet" />
          {{ partial "head_custom.html" . }}
        </head>
      
        <body>
          <nav>
          <ul class="menu">
            {{ range .Site.Menus.main }}
            <li><a href="{{ .URL | relURL }}">{{ .Name }}</a></li>
            {{ end }}
          </ul>
          <hr/>
          </nav>

      如果您熟悉 HTML,<head> 区域应该很容易理解。请注意,我们还包含了一个部分模板 head_custom.html,该模板在此主题中为空,但它将使用户更容易向 <head> 添加自定义代码,而无需重写整个模板。更多详细信息请参见 Section 2.6

      导航菜单本质上是一个列表,列表中的每个项目都是从变量 .Site.Menus.main 中读取的。这意味着用户可以在 config.toml 中定义菜单,例如,

      [[menu.main]]
          name = "Home"
          url = "/"
      [[menu.main]]
          name = "About"
          url = "/about/"

      它会生成一个像这样的菜单:

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

      Hugo 拥有强大的菜单系统,而我们在这个主题中只使用了最简单的菜单类型。如果您对嵌套菜单等更多功能感兴趣,请参阅 http://gohugo.io/extras/menus/ 上的完整文档。

    • footer.html 定义页面的页脚区域并关闭 HTML 文档:

        <footer>
        {{ partial "foot_custom.html" . }}
        {{ with .Site.Params.footer }}
        <hr/>
        {{ . | markdownify }}
        {{ end }}
        </footer>
        </body>
      </html>

      部分模板 foot_custom.html 的用途与 head_custom.html 相同;也就是说,允许用户向 <footer> 添加自定义代码,而无需重写整个模板。

      最后,我们使用变量 .Site.Params.footer 生成页脚。请注意,我们再次使用了 with 函数。回想一下语法 {{ with .Site.Params.footer }}{{ . }}{{ end }}{{if .Site.Params.footer }}{{ .Site.Params.footer }}{{ end }} 的简写。此语法使您无需使用 {{ . }} 两次键入表达式 .Site.Params.footer 作为变量 footer 的占位符,它在我们的 config.toml 文件中定义为站点参数。附加函数 markdownify 可以将 Markdown 转换为 HTML(即 {{ . | markdownify }})。总而言之,这个序列意味着我们可以在 config.toml 中的 params 下使用 Markdown 定义 footer 选项,例如,

      [params]
          footer = "&copy; [Yihui Xie](https://yihui.org) 2017"

有一个特殊的模板 404.html,Hugo 使用它来创建 404 页面(当找不到页面时,显示此页面):

{{ partial "header.html" . }}

404 NOT FOUND

{{ partial "footer.html" . }}

使用上述所有模板,我们将能够从 Markdown 源文件生成一个网站。但是,您不太可能对该网站感到满意,因为 HTML 元素根本没有样式,并且默认外观可能对大多数人来说并不有吸引力。您可能已经注意到,在 header.html 中,我们包含了两个 CSS 文件:/css/style.css/css/fonts.css

您可以在线找到许多可应用于 Hugo 主题的现有开源 CSS 框架。例如,最流行的 CSS 框架可能是 Bootstrap:http://getbootstrap.com。当我设计 XMin 时,我想知道在不使用这些现有框架的情况下我能走多远,因为它们通常非常大。例如,bootstrap.css 在未最小化时有近 10000 行代码。事实证明,我用了大约 50 行 CSS 就能够得到满意的外观,下面我将详细解释:

  • style.css 定义除字体之外的所有样式:

    body {
      max-width: 800px;
      margin: auto;
      padding: 1em;
      line-height: 1.5em;
    }

    页面主体的最大宽度设置为 800 像素,因为过宽的页面难以阅读(800 是我选择的任意阈值)。正文使用 CSS 技巧 margin: auto 居中,这意味着顶部、右侧、底部和左侧边距是自动的。当块元素的左右边距为 auto 时,它将居中。

    /* header and footer areas */
    .menu li { display: inline-block; }
    .article-meta, .menu a {
      text-decoration: none;
      background: #eee;
      padding: 5px;
      border-radius: 5px;
    }
    .menu, .article-meta, footer { text-align: center; }
    .title { font-size: 1.1em; }
    footer a { text-decoration: none; }
    hr {
      border-style: dashed;
      color: #ddd;
    }

    请记住,我们的菜单元素是 header.html 中定义的列表 <ul class="menu">。我将菜单中 <li> 的默认显示样式更改为 inline-block,以便它们将作为内联元素从左到右布局,而不是作为项目符号列表垂直堆叠(默认行为)。

    对于菜单中的链接 (<a>) 和文章的 metadata 区域,将删除默认文本修饰(下划线),并应用浅色背景颜色。border radius 设置为 5 pixels,以便您可以看到每个链接后面有一个微妙的圆角矩形。

    水平线 (<hr>) 设置为浅灰色虚线,以使其在页面上不那么突出。这些规则用于将文章正文与页眉和页脚区域分开。

    /* code */
    pre {
      border: 1px solid #ddd;
      box-shadow: 5px 5px 5px #eee;
      padding: 1em;
      overflow-x: auto;
    }
    code { background: #f9f9f9; }
    pre code { background: none; }

    对于代码块 (<pre>),我应用带有阴影效果的浅灰色边框。每个内联代码元素都有一个非常浅的灰色背景。这些装饰仅仅是出于我自己对代码的特殊兴趣和重视。

    /* misc elements */
    img, iframe, video { max-width: 100%; }
    main { hyphens: auto; }
    blockquote {
      background: #f9f9f9;
      border-left: 5px solid #ccc;
      padding: 3px 1em 3px;
    }
    
    table {
      margin: auto;
      border-top: 1px solid #666;
      border-bottom: 1px solid #666;
    }
    table thead th { border-bottom: 1px solid #ddd; }
    th, td { padding: 5px; }
    tr:nth-child(even) { background: #eee }

    超出页边距的嵌入元素(例如图像和视频)通常很难看,因此我将它们的最大宽度限制为 100%。<main> 中的单词连字符已打开。块引用有一个灰色的左侧边栏和浅灰色的背景。表格默认居中,只有三个水平线:表格的顶部和底部边框以及表格标题的底部边框。表格行呈条纹状,以便于阅读表格,尤其是当表格很宽时。

  • fonts.css 是一个单独的样式表,因为它在网站的外观中起着至关重要的作用,并且您很可能想要自定义此文件。在大多数情况下,您的读者会花费最多的时间阅读页面上的文本,因此使文本易于阅读非常重要。我不是网页设计方面的专家,我只是选择 Palatino 作为正文,并选择 Lucida Console 或 Monaco(以您的系统中可用的为准)作为代码。现在使用谷歌网络字体很常见。您可以尝试一些网络字体,看看是否喜欢其中的任何一个。

    body {
      font-family: "Palatino Linotype", "Book Antiqua", Palatino, serif;
    }
    code {
      font-family: "Lucida Console", Monaco, monospace;
      font-size: 85%;
    }

这两个 CSS 文件放在主题的 static/css/ 目录下。在 HTML 模板 header.html 中,路径 /css/style.css 引用文件 static/css/style.css

最后,该主题在 exampleSite/ 下提供了一个示例站点。目录结构可能有点混乱,因为这是一个主题而不是网站。实际上,exampleSite/ 下的所有内容都应该在网站的根目录下,并且顶级的 hugo-xmin/ 目录应该在该网站的 themes/ 目录下,即:

├── config.toml
├── content/
├── ...
├── themes/
   └── hugo-xmin/

└── ...

示例站点提供了示例 config.toml、主页 _index.md、关于页面 about.mdnote/ 下的两篇文章和 post/ 下的两篇文章。它还覆盖主题中的 foot_custom.html

2.5.2 Implementing more features

XMin 实际上是一个功能强大的主题,但我们知道它对您来说可能太小了。该主题(故意)缺少一些常用功能,如果需要,我们将教您如何自行添加它们。所有这些功能和源代码也可以应用于其他主题。

  • Enable Google Analytics. Hugo 提供了内置的部分模板。对于 XMin,您可以添加

    {{ template "_internal/google_analytics.html" . }}

    到网站根目录下的 layouts/partials/foot_custom.html(而不是 themes/hugo-xmin/),并在 config.toml 中配置 googleAnalytics。有关详细信息,请参阅 https://github.com/yihui/hugo-xmin/pull/3,以及从模板呈现的 JavaScript 的此页面的 HTML 源代码:https://deploy-preview-3--hugo-xmin.netlify.com

  • Enable Disqus comments. 与 Google Analytics 类似,您可以添加内置模板

    {{ template "_internal/disqus.html" . }}

    foot_custom.html,并在 config.toml 中配置 Disqus 短名称。有关详细信息,请参阅 https://github.com/yihui/hugo-xmin/pull/4,并在 https://deploy-preview-4--hugo-xmin.netlify.com 上查看预览。

  • Enable syntax highlighting via highlight.js. 将这添加到 head_custom.html

    <link href="//YOUR-CDN-LINK/styles/github.min.css" rel="stylesheet">

    并将这添加到 foot_custom.html

    <script src="//YOUR-CDN-LINK/highlight.min.js"></script>
    <script src="//YOUR-CDN-LINK/languages/r.min.js"></script>
    
    <script>
    hljs.configure({languages: []});
    hljs.initHighlightingOnLoad();
    </script>

    请记住将 YOUR-CDN-LINK 替换为您首选的 highlight.js CDN 主机的链接,例如 cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0。有关 highlight.js 的更多信息,请参阅其主页:https://highlightjs.org。如果您需要使用其他 CDN 主机,cdnjs.com 是一个不错的选择:https://cdnjs.com/libraries/highlight.js 您还可以查看那里支持哪些语言和 CSS 主题。

    您可能会看到 https://github.com/yihui/hugo-xmin/pull/5 的实际实现,以及 https://deploy-preview-5--hugo-xmin.netlify.com/post/2016/02/14/a-plain-markdown-post/ 上带有语法突出显示的示例页面。

  • Support math expressions through MathJax. 将以下代码添加到 foot_custom.html

    <script src="//yihui.org/js/math-code.js"></script>
    <script async
    src="//YOUR-CDN-LINK/MathJax.js?config=TeX-MML-AM_CHTML">
    </script>

    这需要具备丰富的 JavaScript 知识并熟悉 MathJax 才能完全理解上面的代码,我们将把代码的解释留给 Section B.3

    请记住将 YOUR-CDN-LINK 替换为您首选的 MathJax CDN 主机的链接,例如 cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5

  • Show the table of contents (TOC). 要显示 R Markdown 帖子的目录,您只需将输出格式 blogdown::html_page 和选项 toc: true 添加到 YAML:

    output:
      blogdown::html_page:
        toc: true

    对于普通的 Markdown 帖子,您必须修改模板 single.html。帖子的目录存储在 Hugo 模板变量 .TableOfContents 中。您可能需要一个选项来控制是否显示目录,例如,您可以将选项 toc: true 添加到 Markdown 帖子的 YAML metadata 中以显示目录。可以在 single.html 中的帖子内容之前添加以下代码:

    {{ if .Params.toc }}
    {{ .TableOfContents }}
    {{ end }}

    请参阅 https://github.com/yihui/hugo-xmin/pull/7 了解示例实现。

  • Display categories and tags in a post if provided in its YAML.single.html 中要放置类别和标签的位置添加下面的代码,例如,在 <div class="article-meta"></div> 中。

    <p class="terms">
      {{ range $i := (slice "categories" "tags") }}
      {{ with ($.Param $i) }}
      {{ $i | title }}:
      {{ range $k := . }}
      <a href='{{ relURL (print "/" $i "/" $k | urlize) }}'>{{$k}}</a>
      {{ end }}
      {{ end }}
      {{ end }}
    </p>

    基本上,代码循环遍历 YAML metadata 字段 categoriestags,对于每个字段,其值从 .Param 获取,然后我们使用内部循环以 <a href="/tags/foo/">foo</a> 形式的链接写出术语。

    您可以在 https://github.com/yihui/hugo-xmin/pull/2 上查看完整的实现和预览在 https://deploy-preview-2--hugo-xmin.netlify.com/post/2016 /02/14/a-plain-markdown-post/。

  • Add pagination. 当网站上有大量帖子时,您可能不希望在单个页面上显示完整列表,而是每页显示 N 个帖子(例如,N = 10)。使用 Hugo 的内置函数和模板可以轻松地向网站添加分页。您无需循环遍历列表模板中的所有帖子(例如 range .Data.Pages),而是使用函数 .Paginate 对完整的帖子列表进行分页(例如 range (.Paginate .Data.Pages))。下面是一个模板片段,您可以将其插入到模板文件 list.html 中:

    <ul>
      {{ $paginator := .Paginate .Data.Pages }}
      {{ range $paginator.Pages }}
      <li>
        <span class="date">{{ .Date.Format "2006/01/02" }}</span>
        <a href="{{ .URL }}">{{ .Title }}</a>
      </li>
      {{ end }}
    </ul>
    {{ template "_internal/pagination.html" . }}

    完整实现请参见 https://github.com/yihui/hugo-xmin/pull/16

  • Add a GitHub Edit button or link to a page. 如果上述功能对您来说都不令人兴奋(这不会让我感到惊讶),那么这个小功能确实是一个很好的例子,它向您展示了纯文本文件和静态网站与 GitHub(或其他支持纯文本文件在线编辑的服务)结合使用时的强大功能。我相信在 WordPress 这样的动态网站框架中实现这个功能即使不是不可能,也是很困难的。

    基本上,当您浏览 GitHub 上存储库中的任何文本文件时,如果您有 GitHub 帐户,则可以通过单击 Edit 按钮直接在页面上编辑它们(示例见 Figure 2.3)。如果您对存储库有写权限,则可以直接在线提交更改,否则 GitHub 会自动为您 fork 存储库,以便您可以在自己的存储库中编辑文件,并且 GitHub 会引导您创建 pull request 原始存储库。当原所有者看到 pull request 时,他/她可以看到您所做的更改并决定是否接受它们或要求您进行进一步的更改。尽管术语 “pull request” 对初学者来说非常令人困惑,8 它可能是 GitHub 发明的最伟大的功能,因为它使人们更容易做出贡献。

    真正方便的是,您只需要一个固定形式的 URL 即可编辑 GitHub 上的文件:https://github.com/USER/REPO/edit/BRANCH/PATH/TO/FILE。例如,https://github.com/rbind/yihui/edit/master/content/knitr/faq.md,其中 USERrbindREPOyihuiBRANCHmaster,文件路径为 content/knitr/faq.md

    实现此功能的关键是变量 .File.Path,它为我们提供了 content/ 下页面的源文件路径,例如 post/foo.md。如果您的网站仅使用纯 Markdown 文件,那么实现将非常简单。我在下面的 ... 中省略了完整的 GitHub URL,其中一个示例可以是 https://github.com/rbind/yihui/edit/master/content/

    {{ with .File.Path }}
    <a href="https://github.com/.../{{ . }}">Edit this page</a>
    {{ end }}

    然而,当涉及 R Markdown 帖子时,对于 blogdown 用户来说,情况会稍微复杂一些。您不能只使用 .File.Path,因为它实际上指向 .Rmd 文件的 .html 输出文件,而 .Rmd 文件是实际的源文件。编辑按钮或链接不应指向 .html 文件。以下是完整的实现,您可以根据您想要显示编辑链接的位置(例如 footer.html)将其添加到模板文件中:

    {{ if .File.Path }}
    
    {{ $Rmd := (print .File.BaseFileName ".Rmd") }}
    
    {{ if (where (readDir (print "content/" .File.Dir)) "Name" $Rmd) }}
      {{ $.Scratch.Set "FilePath" (print .File.Dir $Rmd) }}
    {{ else }}
      {{ $.Scratch.Set "FilePath" .File.Path }}
    {{ end }}
    
    {{ with .Site.Params.GithubEdit}}
    <a href='{{ . }}{{ $.Scratch.Get "FilePath" }}'>Edit this page</a>
    {{ end }}
    
    {{ end }}

    基本逻辑是,对于一个文件,如果存在扩展名为 .Rmd 的相同文件名,我们会将编辑链接指向该 Rmd 文件。首先,我们定义变量 $Rmd 作为扩展名为 .Rmd 的文件名。然后我们检查它是否存在。不幸的是,Hugo 中没有像 R 中的 file.exists() 这样的函数,所以我们必须使用一个 hack:列出目录下的所有文件,并查看 Rmd 文件是否在列表中。$.Scratch 是 Hugo 模板中动态存储和获取变量的方式。Hugo 中的大多数变量都是只读的,当你想修改变量时你必须使用 $.Scratch。我们在 $.Scratch 中设置一个变量 FilePath,当 Rmd 文件存在时,其值为 Rmd 文件的完整路径,否则为 Markdown 源文件的路径。最后,我们将 config.toml 中的自定义选项 GithubEdit 与文件路径连接起来,完成编辑链接 <a>。以下是 config.toml 中的选项示例:

    [params]
      GithubEdit = "https://github.com/rbind/yihui/edit/master/content/"

    请注意,如果您在 Windows 上使用 Hugo 来构建和部署站点,您可能需要将文件路径分隔符从反斜杠更改为正斜杠,例如,您可能需要 {{ $.Scratch.Set "FilePath" (replace ($.Scratch.Get "FilePath") "\\" "/") }} 在模板中。为了避免这种复杂情况,我们不建议您通过 Windows 部署站点(有关部署方法,请参阅 Chapter 3)。

    您可以查看 https://github.com/yihui/hugo-xmin/pull/6 以了解 R Markdown 示例的实际实现,并查看本页页脚的编辑链接:https://deploy-preview-6 –hugo-xmin.netlify.com。实际上,您可以在每个页面的页脚中看到一个链接,页面列表除外(因为它们没有源文件)。

Edit a text file online on GitHub.

FIGURE 2.3: Edit a text file online on GitHub.

当你消化了 XMin 主题和附加功能的实现后,理解其他人的模板应该会容易得多。Hugo 主题有很多,但它们之间的主要区别通常在于风格。模板的基本组件通常是相似的。


  1. 模板最常见的功能是呈现 HTML 页面,但也可以有特殊的模板,例如用于 RSS 提要和站点地图(它们是 XML 文件)的模板。↩︎

  2. 在我看来,它确实应该被称为 “merge request”。↩︎