I’ve previously implemented a series taxonomy in Hugo, but it only supported a single series per post and used intersect
which is kind of slow.
Here is my improved implementation of a series taxonomy in Hugo 👇
Table of contents
Configuration
As before; all taxonomies must be defined in the configuration:
[taxonomies]
tag = "tags"
series = "series"
controller = "controllers"
This allows us to use series
in the front matter, and a /series/
page gets created — like mine.
All taxonomies must be defined as arrays in the front matter, but unlike the previous implementation, this solution actually support multiple series 🙂
series = ["Home network v2", "Network cabling"]
Layout
I’m showing a notification at the top of the page, and a list of all pages part of the series at the bottom. So we need to add some code to our /layouts/_default/single.html
layout.
Before the content
Here we are using range
to build up a slice
(array) with links to all post series, and then showing them using delimit
:
{{- with (.GetTerms "series") -}}
{{- $series := slice -}}
{{- range . -}}
{{- $series = $series | append (printf "<a href=\"%s\">%s</a>" .Permalink .Title) -}}
{{- end -}}
<div class="notice--info post-series-top">
<p>This post is part of the {{ (delimit $series ", " " and ") | safeHTML }} series.</p>
</div>
{{- end -}}
And a bit of CSS to make it look pretty:
@mixin notice($notice-color)
{
margin: 1em 0;
padding: 0.5em 1em;
border-left: 0.5em solid $notice-color;
background-color: rgba($notice-color, 0.1);
> *:first-child {
margin-top: 0;
}
> *:last-child {
margin-bottom: 0;
}
}
.notice--info {
@include notice(#8ebeeb);
}
div.post-series-top {
a {
font-weight: bold;
}
}
And we have a notification, showing all series titles, with link to the term pages 👍
After the content
In single.html
I used a partial:
{{- if .Params.series -}}
<div class="post-series-bottom">
{{ partial "series.html" . }}
</div>
{{- end -}}
Which loads the code from layouts/partials/series.html
. In my previous implementation I was using intersect
— which is a poor choice as it’s slow and returns the common elements of two arrays or slices. We don’t really need that.
So instead I’m first using range
to loop through the series, and then range
again to loop through all pages within that series. All pages are shown as links — except the page you are on, it’s highlighted in bold:
{{- with (.GetTerms "series") -}}
{{- range . -}}
<h3>{{ .Title }} series</h3>
{{- $series := .Pages.ByDate -}}
<ol>
{{- range $series -}}
<li>
{{- if eq .File.UniqueID $.File.UniqueID -}}
<b>{{ .Title }}</b>
{{- else -}}
<a href="{{ .Permalink }}">{{ .Title }}</a>
{{- end -}}
</li>
{{- end -}}
</ol>
{{- end -}}
{{- end -}}
Done
I like to have some freedom with my taxonomies, free of constraints like having to add, or only using one. This is why I’ve decided against trying to categorize posts on this site. Some posts fits easily into a single category, others do not.
With this new series implementation I don’t have to worry about some posts crossing two series 🙂
Last commit 2024-04-15, with message: Minor go template correction to multiple series post.