Introduction
Hugo provides a powerful content management tool called taxonomies. In addition to default taxonomies such as categories and tags, you can define one on your own and customise as you like (see this repo for a cool example!).
The Problem
One important feature of taxonomies is that you can show the list of terms (keywords) across the site, such as the list of tags. By default, the list looks like a regular branch bundle of a section because it follows the same layout:
Now, how can we improve this page? We can certainly reduce the spacing between terms using flexbox and sort them alphabetically.
It looks much better than before, but you can expect this list will grow to a messy clutter of words as the number of tags increases over time. It would look much better if we could divide them into smaller chunks based on the first letter.
The Solution(s)
I want to clarify that this is not the first solution to this problem. There was a discussion on this matter a while ago, and you can also find a different solution. These methods basically sort the titles alphabetically, track the first letter, and start the <ul>
element again whenever it detects a change. I wanted to try a more conventional approach, where you first build a dictionary of tags that look like this:
{
"A": [
"absolute value",
"algebra"
],
"D": [
"decimal",
"distributive law"
],
// ...
}
and build the document using this information.
Classifying the items
We will first build the dictionary, which we call $pages_by_letters
, where the keys are the letters of the alphabet, and the values are the list of tags that start with that letter. Here is the full code:
|
|
Explanation
{{- $letters := split "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "" -}}
{{- $pages := .Pages.ByTitle -}}
{{- $pages_by_letters := dict -}}
{{ range $pages }}
{{- $page := . -}}
{{- $first_letter := upper ( substr $page.Name 0 1 ) -}}
{{- if not (in $letters $first_letter) }}
{{ $first_letter = "#" }}
{{ end }}
{{- $new_list := slice -}}
{{ with index $pages_by_letters $first_letter }}
{{- $new_list = . | append $page -}}
{{ else }}
{{- $new_list = slice $page -}}
{{ end }}
{{- $pages_by_letters = merge $pages_by_letters (dict $first_letter $new_list) -}}
{{ end }}
Let’s first define some useful variables. Here, $letters
is the slice (list) of the English alphabet, and $pages
is the slice of all terms, sorted alphabetically. Then, we will loop over $pages
to look at individual terms.
{{- $letters := split "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "" -}}
{{- $pages := .Pages.ByTitle -}}
{{- $pages_by_letters := dict -}}
{{ range $pages }}
{{- $page := . -}}
{{- $first_letter := upper ( substr $page.Name 0 1 ) -}}
{{- if not (in $letters $first_letter) }}
{{ $first_letter = "#" }}
{{ end }}
{{- $new_list := slice -}}
{{ with index $pages_by_letters $first_letter }}
{{- $new_list = . | append $page -}}
{{ else }}
{{- $new_list = slice $page -}}
{{ end }}
{{- $pages_by_letters = merge $pages_by_letters (dict $first_letter $new_list) -}}
{{ end }}
Because we need to change the scope, we first need to define $page
. Then, $first_letter
, as the name suggests, is the (capitalised) first letter of the name of $page
. A term can start with numbers or non-alphabetic letters, so we need to classify them separately.
{{- $letters := split "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "" -}}
{{- $pages := .Pages.ByTitle -}}
{{- $pages_by_letters := dict -}}
{{ range $pages }}
{{- $page := . -}}
{{- $first_letter := upper ( substr $page.Name 0 1 ) -}}
{{- if not (in $letters $first_letter) }}
{{ $first_letter = "#" }}
{{ end }}
{{- $new_list := slice -}}
{{ with index $pages_by_letters $first_letter }}
{{- $new_list = . | append $page -}}
{{ else }}
{{- $new_list = slice $page -}}
{{ end }}
{{- $pages_by_letters = merge $pages_by_letters (dict $first_letter $new_list) -}}
{{ end }}
Then, we will try to search the dictionary with $first_letter
. If there is an entry, we can attend the current $page
to the entry. Otherwise, we need to make a new slice. The loop ends after we update the dictionary.
Printing the items
We can then make the list from the dictionary. Since we will make use of the CSS grid, the keys can just sit inside a <span>
.
|
|
{{ len .Pages }}
calculates the number of posts for a single tag.
CSS
Finally, we can put everything together using (S)CSS.
|
|