从抛弃 Jekyll 转向 Hugo 搞毫无用处的静态站到现在,全程就是在不停的 Ctrl+CCtrl+V。别人的主题用久了也腻了,于是翻来找去从 printempw 那儿继续 Ctrl+C 了一个主题,不过这次是自己从零开始。

在写分页导航的时候遇到了不少问题,也查了不少资料,简单记录一下。

自定义 Hugo 的分页导航栏

Hugo 内置了 Pagination 导航模板,用内置的模板是一般情况下最省心的解决方案,官方文档提供了两种方案如下:

{{ $paginator := .Paginate (where .Pages "Type" "posts") }}
{{ template "_internal/pagination.html" . }}
{{ range $paginator.Pages }}
    {{ .Title }}
{{ end }}
{{ template "_internal/pagination.html" . }}
{{ range .Paginator.Pages }}
    {{ .Title }}
{{ end }}

在创建普通 list 页面的时候使用下面的方案即可,但在创建例如 Fuji 主题的主页 list 的时候,由于需要对获取的文章进行筛选,因此需要用到上一种解决方案。

对内置模板的小改动

来看一下使用内置导航模板生成的 HTML 内容:

<ul class="pagination">
  <li class="page-item">
    <a href="/" class="page-link" aria-label="First"><span aria-hidden="true">&laquo;&laquo;</span></a>
  </li>
  <li class="page-item disabled">
    <a class="page-link" aria-label="Previous"><span aria-hidden="true">&laquo;</span></a>
  </li>
  <li class="page-item active"><a class="page-link" href="/">1</a></li>
  <li class="page-item"><a class="page-link" href="/page/2/">2</a></li>
  <li class="page-item"><a class="page-link" href="/page/3/">3</a></li>
  <li class="page-item disabled"><span aria-hidden="true">&nbsp;&hellip;&nbsp;</span></li>
  <li class="page-item"><a class="page-link" href="/page/5/">5</a></li>
  <li class="page-item">
    <a href="/page/2/" class="page-link" aria-label="Next"><span aria-hidden="true">&raquo;</span></a>
  </li>
  <li class="page-item">
    <a href="/page/5/" class="page-link" aria-label="Last"><span aria-hidden="true">&raquo;&raquo;</span></a>
  </li>
</ul>

导航栏内部元素中,中间被省略的数字会被添加一个 disabled 类,当处在第一页或最后一页时,相对应的上一页和下一页的链接也会被添加这个类。当前的页面会被添加 active 类。只需根据不同的类进行 CSS 修正即可,例如:

ul.pagination li.page-item {
  display: inline;
  // 移除处于第一页时的前一页和处于最后一页时的下一页
  // 注意:此处会将数字之间的省略号一起移除,仍需完善
  &.disabled {
    display: none;
  }
  // 移除没有大用处的第一页和最后一页的直接跳转链接
  &:first-child,
  &:last-child {
    display: none;
  }
}

如何自建导航栏

对于想要更进一步自定义的情况,内置的分页导航栏就不够用了,需要自建导航栏。

以需要筛选内容的首页为例,首先需要获取分页变量:

{{ $paginator := .Paginator }}

其次可以通过判断有几个分页来决定是否显示分页导航栏:

{{ if gt $paginator.TotalPages 1 }}
    <!-- 分页导航栏 -->
{{ end }}

获取所有的分页代号,并写入导航栏中,并为当前页面添加特殊类:

<ul class="pagination">
    {{ range $paginator.Pagers }}
    <li class="pag-item{{ if eq . $paginator }} pag-current{{ end }}">
        <a href="{{ .URL }}" class="pag-link">{{ .PageNumber }}</a>
    </li>
    {{ end }}
</ul>

添加上一页的链接,下一页同理:

{{ if $paginator.HasPrev }}
    <li class="pag-item pag-previous">
        <a href="{{ $paginator.Prev.URL }}" class="pag-link"></a>
    </li>
{{ end }}

智能导航栏

现在基本的导航栏已经成型了,但是最大的问题是当页面过多的时候,将所有页号都显示出来一定会使导航栏变得很长,为了解决这个问题现在需要这些功能:

  • 当前页面需要显示在导航栏内
  • 当有很多页面时,导航栏将展示出当前页面前后的几页
  • 显示出的页码个数不能超过限制

例如当有十个页面时,并且定义 $offsetLinks = 2 时希望实现如下图的效果:

智能导航栏预览图

  • 最大可显示的页码数 $maxLinks = ($offsetLinks * 2) + 1
  • 如果总页面数小于 $maxLinks,那么所有页码正常显示
  • 当前页面为 1-3 和 8-10 时的显示与其他不同,需要独立定义
  • 以上这条引出上下限变量 $lowerLimit = $offsetLinks + 1$upperLimit = .TotalPages - $offsetLinks

变量

在模板中定义这些变量:

{{ $offsetLinks := 2 }}
<!-- $maxLinks = ($offsetLinks * 2) + 1 -->
{{ $maxLinks := (add (mul $offsetLinks 2) 1) }}
<!-- $lowerLimit = $offsetLinks + 1 -->
{{ $lowerLimit := (add $offsetLinks 1) }}
<!-- $upperLimit = $paginator.TotalPages - $offsetLinks -->
{{ $upperLimit := (sub $paginator.TotalPages $offsetLinks) }}

{{ range $paginator.Pagers }} 的循环内,使用 Hugo 的 scratchpad 来定义一个页码 flag 布尔变量。用这玩意来显示或隐藏页码,默认设置为 false

{{ range $paginator.Pagers }}
    {{ $.Scratch.Set "pageNumFlag" false }}
{{ end }}

使用 .Scratch 的原因是因为 if 段内定义的局部变量外部无法访问。

页码数不够多的情况

如果总页面数小于 $maxLinks,那么所有页码正常显示:

{{ range $paginator.Pagers }}
    {{ $.Scratch.Set "pageNumFlag" false }} <!-- 页码数足够多的情况 -->
    {{ if gt $paginator.TotalPages $maxLinks }}
        <!-- ... -->
    {{ else }} <!-- 页码数不够多的情况 -->
        {{ $.Scratch.Set "pageNumFlag" true }}
    {{ end }}

    {{ if eq ($.Scratch.Get "pageNumFlag") true }}
        <li class="pag-item{{ if eq . $paginator }} pag-current{{ end }}">
            <a href="{{ .URL }}" class="pag-link">{{ .PageNumber }}</a>
        </li>
    {{ end }}
{{ end }}

页码数足够多的情况

以下为基本思路,详情请见最终代码:

<!-- 如果当前页面为例子中的 1-3 区间  -->
{{ if le $paginator.PageNumber $lowerLimit }}
    <!-- ... -->
<!-- 如果当前页面为例子中的 8-10 区间 -->
{{ else if ge $paginator.PageNumber $upperLimit }}
    <!-- ... -->
<!-- 如果当前页面为例子中的 4-7 区间 -->
{{ else }}
    <!-- ... -->
{{ end }}

最终整体代码

<!-- 开始 输出一定数量的位于 posts 分类下的文章 -->
{{ $paginator := .Paginate (where .Data.Pages "Type" "posts") }}
{{ range $paginator.Pages }}
<div class="post">
    <h2 class="post-title">
        <a href="{{ .Permalink }}">{{ .Title }}</a>
    </h2>
    <div class="post-summary">
        {{ .Summary }}
    </div>
</div>
{{ end }}
<!-- 结束 输出一定数量的位于 posts 分类下的文章 -->

<!-- 开始 分页导航 -->
{{ $paginator := .Paginator }}
<!-- 基础偏移变量 -->
{{ $offsetLinks := 2 }}
<!-- $maxLinks = ($offsetLinks * 2) + 1 -->
{{ $maxLinks := (add (mul $offsetLinks 2) 1) }}
<!-- $lowerLimit = $offsetLinks + 1 -->
{{ $lowerLimit := (add $offsetLinks 1) }}
<!-- $upperLimit = $paginator.TotalPages - $offsetLinks -->
{{ $upperLimit := (sub $paginator.TotalPages $offsetLinks) }}

<!-- 如果有超过一页的内容 (即需要导航栏) -->
{{ if gt $paginator.TotalPages 1 }}
<ul class="pagination">
    <!-- 上一页 -->
    {{ if $paginator.HasPrev }}
    <li class="pag-item pag-previous">
        <a href="{{ $paginator.Prev.URL }}" class="pag-link">«</a>
    </li>
    {{ end }}

    <!-- 数字页码部分 -->
    {{ range $paginator.Pagers }}
    {{ $.Scratch.Set "pageNumFlag" false }}
    <!-- 页码数足够多的情况 -->
    {{ if gt $paginator.TotalPages $maxLinks }}
        <!-- 如果当前页面为例子中的 1-3 区间  -->
        {{ if le $paginator.PageNumber $lowerLimit }}
            {{ if le .PageNumber $maxLinks }}
            {{ $.Scratch.Set "pageNumFlag" true }}
            {{ end }}
        <!-- 如果当前页面为例子中的 8-10 区间 -->
        {{ else if ge $paginator.PageNumber $upperLimit }}
            {{ if gt .PageNumber (sub $paginator.TotalPages $maxLinks) }}
            {{ $.Scratch.Set "pageNumFlag" true }}
            {{ end }}
        <!-- 如果当前页面为例子中的 4-7 区间 -->
        {{ else }}
            {{ if and ( ge .PageNumber (sub $paginator.PageNumber $offsetLinks) ) ( le .PageNumber (add $paginator.PageNumber $offsetLinks) ) }}
            {{ $.Scratch.Set "pageNumFlag" true }}
            {{ end }}
        {{ end }}
    <!-- 页码数不够多的情况 -->
    {{ else }}
        {{ $.Scratch.Set "pageNumFlag" true }}
    {{ end }}
    <!-- 输出页码 -->
    {{ if eq ($.Scratch.Get "pageNumFlag") true }}
    <li class="pag-item{{ if eq . $paginator }} pag-current{{ end }}">
        <a href="{{ .URL }}" class="pag-link">
            {{ .PageNumber }}
        </a>
    </li>
    {{ end }}
    {{ end }}

    <!-- 下一页 -->
    {{ if $paginator.HasNext }}
    <li class="pag-item pag-next">
        <a href="{{ $paginator.Next.URL }}" class="pag-link">»</a>
    </li>
    {{ end }}
</ul>
{{ end }}

参考