Implementing a Series in Hugo

August 3, 2017
7 min. read

Goal

In converting my Pelican blog over to Hugo, I needed to learn a new templating system. I miss a few things that Pelican did, like categories based on folders and not front matter. However, I can see advantages of allowing more organization in these folders, without affecting final position on the web.

One glaring miss was the idea of series links. I have been trying to add photos and complete conversion of my travel journal into posts for my Trans-Am bicycle trip in 2002. I wanted the bottom of the entry to allow the reader to jump to the next post.

My goal was to have something like this at the bottom of any series page:

Part [Current Page Num] of [Total Series Page Count] in the [Series Name] series.
< Series Start > | < [Previous Page Title]  |  [Next Page Title] >

I would like < Series Start >, Previous Page Title, Next Page Title, and Series Name have hyperlinks to the relevant pages. I was able to accomplish all but the last. I’m not sure if I want to map the Series Name to category or make a page that lists all posts of a series.

For my Trans-Am, this would render like:

Part 6 of 19 in the Trans-Am series.
< Series Start >  |  < Day 2 - Sherwood Forest Plantation  |  Day 4 - Ashland to Lake Anna, VA >

Implementation

The template I came up with seems very inefficient. Once I get more familiar with go templating, there may be a cleaner way of accomplishing this. If you have improvements, I’d really like to hear about them.

The first thing I need to do is detect if the page is part of a series and the name of that series. I’m printing this value out, so I need to make this to be ready to print. I’m using a series page variable in the front matter for this.

+++
series = "Trans-Am"
+++

In addition to this, I will be sorting based on date for page position in series. date is already a defined variable, so that is handled. I’m adding this functionality as part of my theme, in a new file: layouts/partials/series_link.html. I added a call to this in layouts/_default/single.html just below {{ .Content }} like the following:

<div id="post-content">
    {{ .Content }}
    {{ partial "series_link.html" . }}
</div>

This will put it directly after the main page content.

Complete source, which I will discuss in parts below:

{{ if .Params.series }}
    {{ $.Scratch.Add "cur_page_num" 1 }} 
    {{ $.Scratch.Add "total_page_num" 0 }} 
    {{ range where .Site.RegularPages.ByDate "Params.series" .Params.series }} 
        {{ $.Scratch.Add "total_page_num" 1 }} 
        {{ if gt $.Date.Unix .Date.Unix }} 
            {{ $.Scratch.Add "cur_page_num" 1 }} 
            {{ $.Scratch.Set "prev_link" .Permalink }}
            {{ $.Scratch.Set "prev_title" .Title }}  
        {{ end }}
    {{ end }}
    {{ range where .Site.RegularPages.ByDate.Reverse "Params.series" .Params.series }} 
        {{ $.Scratch.Set "first_link" .Permalink }}
        {{ if lt $.Date.Unix .Date.Unix }}
            {{ $.Scratch.Set "next_link" .Permalink }}
            {{ $.Scratch.Set "next_title" .Title }}
        {{ end }}
    {{ end }}
    {{ if or ($.Scratch.Get "next_link") ($.Scratch.Get "prev_link") }} 
        <hr/>
        <p>Part {{ $.Scratch.Get "cur_page_num" }} of {{ $.Scratch.Get "total_page_num" }}
            in the <b>{{ .Params.series }}</b> series.</p>
        <p>
        {{ if $.Scratch.Get "prev_link" }}
            {{ if ne ($.Scratch.Get "prev_link") ($.Scratch.Get "first_link") }}
                <a href="{{ $.Scratch.Get "first_link" }}"><i class="fa fa-angle-left"></i>Series Start<i class="fa fa-angle-right"></i></a>
                    |
            {{ end }}
            {{ if $.Scratch.Get "prev_link" }}
                <a href="{{ $.Scratch.Get "prev_link" }}"><i class="fa fa-angle-double-left"></i>{{ $.Scratch.Get "prev_title" }}</a>
            {{ end }}
        {{ end }}
        {{ if and ($.Scratch.Get "next_link") ($.Scratch.Get "prev_link") }}
                |
        {{ end }}
        {{ if $.Scratch.Get "next_link" }}
            <a href="{{ $.Scratch.Get "next_link" }}">{{ $.Scratch.Get "next_title" }}<i class="fa fa-angle-double-right"></i></a></p>
        {{ end }}
    {{ end }}
{{ end }}

Enabling

The outer if only allows this to run if series exists on a page:

{{ if .Params.series }}
   ...
{{ end }}

Getting Data

Next I am defining page variables to hold the current page number of the series and the total pages in the series.

{{ $.Scratch.Add "cur_page_num" 1 }}
{{ $.Scratch.Add "total_page_num" 0 }}

Here we are looping through pages by date (old to new), where the page’s series is the same as the current page’s series. total_page_num is incremented each loop, yielding a count of all pages that are in the series.

Our if is true when the current page date is greater than the looping page. This will apply to all pages previous to the current page. So incrementing cur_page_num from 0 would make it the number of the previous page. This is why it is initialized to 1 above. I’m also saving the link and title of the page. This is set each page, but will last be set when it reaches the previous page, after which the if will be in a false state.

{{ range where .Site.RegularPages.ByDate "Params.series" .Params.series }}
        {{ $.Scratch.Add "total_page_num" 1 }}
    {{ if gt $.Date.Unix .Date.Unix }}
        {{ $.Scratch.Add "cur_page_num" 1 }}
        {{ $.Scratch.Set "prev_link" .Permalink }}
        {{ $.Scratch.Set "prev_title" .Title }}
    {{ end }}
{{ end }}

The next loop is backwards, due to the .Reverse, so it gives pages by date (new to old). I’m setting first_link each time, so it will be the first in the series when complete.

If the current page is less than the page in the loop (meaning it is older), we set the link and title for the next page. This will end up with the page directly next of the current one, then the if is not true for the rest of the loop.

{{ range where .Site.RegularPages.ByDate.Reverse "Params.series" .Params.series }}
    {{ $.Scratch.Set "first_link" .Permalink }}
    {{ if lt $.Date.Unix .Date.Unix }}
        {{ $.Scratch.Set "next_link" .Permalink }}
        {{ $.Scratch.Set "next_title" .Title }}
    {{ end }}
{{ end }}

Generating Display

Now we have all the data we need and we just have to display it on the rendering page.

I don’t want to display anything if there is only one member of the series. So we have a wrapper if to require either a next or a previous link.

{{ if or ($.Scratch.Get "next_link") ($.Scratch.Get "prev_link") }} 
   ...
{{ end }}

I printing an <hr/> to seperate this a little from the bottom of the page. Next, I print the Part x of y in the [SeriesName] series. Since I’m just printing the .Params.series variable directly, it must be display ready as I mentioned above.

<hr/>
<p>Part {{ $.Scratch.Get "cur_page_num" }} of {{ $.Scratch.Get "total_page_num" }}
    in the <b>{{ .Params.series }}</b> series.</p>

We only want to print a < Series Start > and/or < [Previous Title], if there is a previous link. So the outer if checks this.

I want to display the previous title instead of just < Series Start > if they are the same thing. So I’m only showing the start link if prev_link and first_link are not equal (ne). Since if they are not equal, I know both exist, I include the | delimiter inside that if.

The second if just displays the previous link if it exists.

So for the first page in the series, this section is skipped with no prev_link set. For the second page in the series: prev_link == first_link, so the < Series Start > isn’t printed and we get a prev_title text link to the first page. For third and subsequent pages, we will get both of these links.

{{ if $.Scratch.Get "prev_link" }}
    {{ if ne ($.Scratch.Get "prev_link") ($.Scratch.Get "first_link") }}
        <a href="{{ $.Scratch.Get "first_link" }}"><i class="fa fa-angle-left"></i>Series Start<i class="fa fa-angle-right"></i></a>
            |  
    {{ end }}
    {{ if $.Scratch.Get "prev_link" }}
        <a href="{{ $.Scratch.Get "prev_link" }}"><i class="fa fa-angle-double-left"></i>{{ $.Scratch.Get "prev_title" }}</a>
    {{ end }}
{{ end }}

This section prints the delimiting | between next_link and prev_link, only if both exist.

{{ if and ($.Scratch.Get "next_link") ($.Scratch.Get "prev_link") }}

{{ end }}

The last section just prints the link to the next page, if next_link exists.

{{ if $.Scratch.Get "next_link" }} 
    <a href="{{ $.Scratch.Get "next_link" }}">{{ $.Scratch.Get "next_title" }}<i class="fa fa-angle-double-right"></i></a></p>
{{ end }}

That completes the template.

You can see this on any one of my Trans-Am pages, at the bottom of the page.


Part 2 of 3 in the Hugo series.

Moved from Pelican to Hugo | Disabling Analytics for Local Hugo

comments powered by Disqus