导航

2017 年 1 月 24 日 tomjoht tomjoht

如果你的 Jekyll 网站有很多页面,你可能希望为这些页面创建导航。你可以按编程方式检索页面列表来为你的网站构建导航,而无需对导航链接进行硬编码。

虽然其他 Jekyll 文档中已经提供了有关 与数据文件交互 的信息,但本教程将深入探讨如何为你的网站构建更强大的导航。

有两种主要方法可以在 Jekyll 网站上检索页面

  • 检索在 YAML 数据源中列出的页面。将页面数据存储在 _data 文件夹中的 YAML(或 JSON 或 CSV)文件中,循环遍历 YAML 属性,并将值插入到你的主题中。
  • 通过循环遍历页面前端信息来检索页面。查看页面的前端信息以识别某些属性,返回这些页面,并将页面的前端信息值插入到你的主题中。

以下示例从一个基本的导航场景开始,并添加更复杂的元素来演示返回页面的不同方法。在每种情况下,你都将看到 3 个元素

  • YAML
  • Liquid
  • 结果

_data 目录中的 YAML 文件称为 samplelist.yml

场景如下

场景 1:基本列表

您希望返回一个基本页面列表。

YAML

docs_list_title: ACME Documentation
docs:

- title: Introduction
  url: introduction.html

- title: Configuration
  url: configuration.html

- title: Deployment
  url: deployment.html

Liquid

<h2>{{ site.data.samplelist.docs_list_title }}</h2>
<ul>
   {% for item in site.data.samplelist.docs %}
      <li><a href="{{ item.url }}">{{ item.title }}</a></li>
   {% endfor %}
</ul>

结果

ACME 文档

对于这些虚构示例中的结果,# 被手动替换为实际链接值(以避免 404 错误)。

当您使用 for 循环时,您可以选择如何引用您循环遍历的项目。您选择的变量(在本例中为 item)将成为您访问列表中每个项目属性的方式。点表示法用于获取项目的属性(例如,item.url)。

YAML 内容有两种主要格式与这里相关

  • 映射
  • 列表

docs_list_title: ACME Documentation 是一个映射。您可以使用 site.data.samplelist.docs_list_title 访问该值。

docs: 是一个列表。该列表以连字符开头。与映射不同,您通常不会像使用映射那样直接访问列表属性。如果您想访问列表中的特定项目,您必须识别您想要的列表中的位置,遵循典型的数组表示法。例如,site.data.samplelist.docs[0] 将访问列表中的第一个项目。但是,这很少会这样做。

对于列表,您通常使用 for 循环来循环遍历项目列表,并对每个项目执行某些操作。对于导航菜单,您通常会根据 HTML 主题中使用的导航结构将每个列表项插入到 li 标记中。

每个连字符 (-) 表示列表中的另一个项目。此示例仅在每个列表项中具有两个属性:titleurl。您可以为每个项目包含任意数量的属性。列表中每个位置的属性顺序无关紧要。

场景 2:已排序列表

假设您想按 title 对列表进行排序。为此,将对 docs 集合的引用转换为变量,然后将 Liquid 的 sort 过滤器应用于该变量

Liquid

{% assign doclist = site.data.samplelist.docs | sort: 'title'  %}
<ol>
{% for item in doclist %}
    <li><a href="{{ item.url }}">{{ item.title }}</a></li>
{% endfor %}
</ol>

结果

项目现在按字母顺序显示。sort 属性在 Liquid 过滤器中应用于 title,这是列表中的实际属性。如果 title 不是属性,我们需要按其他属性排序。

请参阅 Liquid 数组过滤器 了解更多过滤器选项。请注意,您不能简单地使用此语法

{% for item in site.data.samplelist.docs | sort: "title" %}{% endfor %}

您必须先使用 assigncapture 标记将 site.data.samplelist.docs 转换为变量。

场景 3:两级导航列表

假设您想要一个更强大的列表,其中包含标题和子项的多个部分。为此,请为每个列表项添加一个附加级别来存储此信息

YAML

toc:
  - title: Group 1
    subfolderitems:
      - page: Thing 1
        url: /thing1.html
      - page: Thing 2
        url: /thing2.html
      - page: Thing 3
        url: /thing3.html
  - title: Group 2
    subfolderitems:
      - page: Piece 1
        url: /piece1.html
      - page: Piece 2
        url: /piece2.html
      - page: Piece 3
        url: /piece3.html
  - title: Group 3
    subfolderitems:
      - page: Widget 1
        url: /widget1.html
      - page: Widget 2
        url: /widget2.html
      - page: Widget 3
        url: /widget3.html

Liquid

{% for item in site.data.samplelist.toc %}
    <h3>{{ item.title }}</h3>
      <ul>
        {% for entry in item.subfolderitems %}
          <li><a href="{{ entry.url }}">{{ entry.page }}</a></li>
        {% endfor %}
      </ul>
  {% endfor %}

结果

在此示例中,组 1 是第一个列表项。在该列表项中,其子页面被包含为一个属性,该属性本身包含一个列表 (subfolderitems)。

Liquid 代码通过 for item in site.data.samplelist.toc 查看第一层,然后通过 for entry in item.subfolderitems 查看第二层属性。就像 item 是我们循环遍历的项目的任意名称一样,entry 也是如此。

场景 4:三级导航列表

在上一部分的基础上,让我们为列表添加一个更深的级别 (subsubfolderitems)。这里的格式会变得更加复杂,但原理是一样的。

YAML

toc2:
  - title: Group 1
    subfolderitems:
      - page: Thing 1
        url: /thing1.html
      - page: Thing 2
        url: /thing2.html
        subsubfolderitems:
          - page: Subthing 1
            url: /subthing1.html
          - page: Subthing 2
            url: /subthing2.html
      - page: Thing 3
        url: /thing3.html
  - title: Group 2
    subfolderitems:
      - page: Piece 1
        url: /piece1.html
      - page: Piece 2
        url: /piece2.html
      - page: Piece 3
        url: /piece3.html
        subsubfolderitems:
          - page: Subpiece 1
            url: /subpiece1.html
          - page: Subpiece2
            url: /subpiece2.html
  - title: Group 3
    subfolderitems:
      - page: Widget 1
        url: /widget1.html
        subsubfolderitems:
          - page: Subwidget 1
            url: /subwidget1.html
          - page: Subwidget 2
            url: /subwidget2.html
      - page: Widget 2
        url: /widget2.html
      - page: Widget 3
        url: /widget3.html

Liquid

<div>
{% if site.data.samplelist.toc2[0] %}
  {% for item in site.data.samplelist.toc2 %}
    <h3>{{ item.title }}</h3>
      {% if item.subfolderitems[0] %}
        <ul>
          {% for entry in item.subfolderitems %}
              <li><a href="{{ entry.url }}">{{ entry.page }}</a>
                {% if entry.subsubfolderitems[0] %}
                  <ul>
                  {% for subentry in entry.subsubfolderitems %}
                      <li><a href="{{ subentry.url }}">{{ subentry.page }}</a></li>
                  {% endfor %}
                  </ul>
                {% endif %}
              </li>
          {% endfor %}
        </ul>
      {% endif %}
    {% endfor %}
{% endif %}
</div>

结果

在此示例中,if site.data.samplelist.toc2[0] 用于确保 YAML 级别实际上包含项目。如果没有内容位于 [0] 位置,我们可以跳过在此级别中查找。

专业提示:对齐 for 循环和 if 语句

为了保持代码清晰,请对齐开始和结束的 Liquid 标签,例如 for 循环和 if 语句。这样,您就知道何时关闭了打开的标签。如果代码将出现在 Markdown 页面中,请将 HTML 开始和结束标签与左边缘对齐,以便 Markdown 过滤器不会将内容视为代码示例。如有必要,您可以将整个代码示例包装在 div 标签中,以确保代码具有将代码括起来的 HTML 标签。

场景 5:使用页面变量选择 YAML 列表

假设您的侧边栏会根据不同的文档集而有所不同。您的网站上可能有 3 种不同的产品,因此您需要 3 个不同的侧边栏——每个侧边栏都针对该产品而设计。

您可以将侧边栏列表的名称存储在页面前置内容中,然后将该值动态地传递到列表中。

页面前置内容

---
title: My page
sidebar: toc
---

Liquid

<ul>
    {% for item in site.data.samplelist[page.sidebar] %}
      <li><a href="{{ item.url }}">{{ item.title }}</a></li>
    {% endfor %}
</ul>

结果

在此场景中,我们希望将页面前置内容中的值传递到包含变量的 for 循环中。当分配的变量不是字符串而是数据引用时,您必须使用方括号(而不是大括号)来引用前置内容的值。

有关更多信息,请参阅 Liquid 文档中的 表达式和变量。在无法使用点表示法的地方使用方括号。您还可以在此 Stack Overflow 回答 中阅读更多详细信息。

场景 6:为当前页面应用活动类

除了将 YAML 数据文件中的项目插入到您的列表中之外,您通常还希望在用户查看该页面时突出显示当前链接。您可以通过为与当前页面 URL 匹配的项目插入 active 类来实现此目的。

CSS

.result li.active a {
    color: lightgray;
    cursor: default;
}

Liquid

{% for item in site.data.samplelist.docs %}
    <li class="{% if item.url == page.url %}active{% endif %}">
      <a href="{{ item.url }}">{{ item.title }}</a>
    </li>
{% endfor %}

结果

在这种情况下,假设 Deployment 是当前页面。

为了确保 item.url(存储在 YAML 文件中)与 page.url 匹配,将 {{ page.url }} 打印到页面中会很有帮助。

场景 7:有条件地包含项目

您可能希望有条件地将项目包含在您的列表中。例如,您可能有多个网站输出,并且只想为某些输出包含侧边栏项目。您可以在每个列表项中添加属性,然后使用这些属性有条件地包含内容。

YAML

docs2_list_title: ACME Documentation
docs2:

- title: Introduction
  url: introduction.html
  version: 1

- title: Configuration
  url: configuration.html
  version: 1

- title: Deployment
  url: deployment.html
  version: 2

Liquid

  <ul>
    {% for item in site.data.samplelist.docs2 %}
      {% if item.version == 1 %}
        <li><a href="{{ item.url }}">{{ item.title }}</a></li>
      {% endif %}
    {% endfor %}
</ul>

结果

Deployment 页面被排除在外,因为其 version2

场景 8:基于前端信息属性检索项目

如果您不想将导航项目存储在 _data 文件夹中的 YAML 文件中,则可以使用 for 循环来查看每个页面或集合的前置内容,并根据前置内容中的属性获取内容。

在此场景中,假设我们有一个名为 _docs 的集合。集合通常比页面更好,因为它们允许您缩小循环范围。(尽量避免循环处理大量项目的情况,因为这会增加您的构建时间。 集合 帮助您缩小范围。)

在我们的场景中,docs 集合中有 6 个文档:示例 1、示例 2、主题 1、主题 2、小部件 1 和小部件 2。

集合中的每个文档都包含正文中的至少 3 个属性

  • 标题
  • 类别
  • 顺序

每个页面的正文如下(在此处合并以简化内容)

---
Title: Sample 1
category: getting-started
order: 1
---

---
Title: Sample 2
category: getting-started
order: 2
---

---
Title: Topic 1
category: configuration
order: 1
---

---
Title: Topic 2
category: configuration
order: 2
---

---
Title: Widget 1
category: deployment
order: 1
---

---
Title: Widget 2
category: deployment
order: 2
---

请注意,即使正文中使用了 categorycategory 也不是像文章中那样的内置变量。换句话说,您不能使用 site.docs.category 直接查看 category 的内部内容。

如果您只想获取特定类别中的集合中的所有文档,可以使用 for 循环和 if 条件来检查特定类别

<h3>Getting Started</h3>
<ul>
    {% for doc in site.docs %}
      {% if doc.category == "getting-started" %}
        <li><a href="{{ doc.url }}">{{ doc.title }}</a></li>
      {% endif %}
    {% endfor %}
</ul>

结果如下

入门

如果您正在设置一个知识库,并且每个类别中都有几十个主题,每个类别都在其自己的页面上显示,这可能很有用。

但是,假设您想按类别对项目进行排序,并将它们分组在类别名称下,而无需对类别名称进行硬编码。为此,您可以使用两个过滤器

  • group_by
  • sort

以下是获取按相应类别标题分组的页面列表的代码

Liquid

{% assign mydocs = site.docs | group_by: 'category' %}
{% for cat in mydocs %}
<h2>{{ cat.name | capitalize }}</h2>
    <ul>
      {% assign items = cat.items | sort: 'order' %}
      {% for item in items %}
        <li><a href="{{ item.url }}">{{ item.title }}</a></li>
      {% endfor %}
    </ul>
{% endfor %}

结果

让我们逐步了解代码。首先,我们将一个变量 (mydocs) 分配给集合内容 (site.docs)。

group_by 过滤器按 category 对集合内容进行分组。更具体地说,group_by 过滤器将 mydocs 转换为一个数组,其中包含 nameitemssize 属性,如下所示

[
  {"name": "getting-started", "items": [Sample 1, Sample 2],"size": 2},
  {"name": "configuration", "items": [Topic 1, Topic 2], "size": 2},
  {"name": "deployment", "items": [Widget 1, Widget 2], "size": 2}
]

使用 for cat in mydocs,我们查看 mydocs 数组中的每个项目,并打印类别 name

获取类别名称后,我们为文档分配变量 items,并使用 sort 过滤器按 order 属性对文档进行排序。点符号 cat.items 用于访问 items 数组中的内容。 sort 过滤器按数字升序对项目进行排序。

for item in items 循环查看每个 item,并获取 titleurl 以形成列表项链接。

有关 group_by 过滤器的更多详细信息,请参阅 Jekyll 模板文档 以及 此 Siteleaf 教程。有关 sort 过滤器的更多详细信息,请参阅 Liquid 文档中的 sort

无论您是在文档的前端内容中使用属性来检索页面还是使用 YAML 数据文件,在这两种情况下,您都可以通过编程方式为您的网站构建更强大的导航。

场景 9:使用递归进行嵌套树导航

假设您想要任意深度的嵌套树导航。我们可以通过递归循环遍历我们的导航链接树来实现这一点。

YAML

nav:
  - title: Deployment
    url: deployment.html
    subnav:
      - title: Heroku
        url: heroku.html
        subnav:
          - title: Jekyll on Heroku
            url: jekyll-on-heroku.html
  - title: Help
    url: help.html

Liquid

首先,我们将创建一个可用于呈现导航树的 include。此文件将为 _includes/nav.html

<ul>
  {% for item in include.nav %}
    <li><a href="{{ item.url }}">{{ item.title }}</a>
      {% if item.subnav %}
        {% include nav.html nav=item.subnav %}
      {% endif %}
    </li>
  {% endfor %}
</ul>

要在您的布局或页面中呈现此内容,您只需包含模板并传入 nav 参数。在这种情况下,我们将使用 page.nav 从 yaml 前端内容中获取它。

{% include nav.html nav=page.nav %}

我们的 include 将首先使用此内容,然后查看每个项目的 subnav 属性以递归呈现嵌套列表。

结果