Comment Créer Un Site Avec Hugo Partie 6 : Le Template Des Listes

Aldok

| 15 minutes

Revenir à l'index du tuto : Creer un site avec hugo

Créer un site avec Hugo partie 6 : le template des listes et taxonomies

Nous avons vu comment gérer le template et les contenus des articles de blog, nous avons également vu comment créer des pages uniques disposant de leur propre template, voyons maintenant un autre “pilier” de tout blog ou site internet : les pages d'index des taxonomies (ou pour faire plus simple : les pages de catégories ou de tags, qui listent les autres pages qui y sont rattachées)

Petit rappel sur le terme “taxonomie” ou “taxinomie”

Si vous avez l'habitude de travailler avec des CMS, vous devez souvent rencontrer ce terme de “taxonomie” ou “taxinomie”.

En tout cas, pour ma part, ça fait des années que je le rencontre et je ne me suis jamais vraiment intéressé à sa définition de base… Le linguiste qui sommeille (profondément) en moi est donc allé faire quelques recherches sur les origines de ce mot.

En ouvrant un dictionnaire, on peut tomber sur cette définition : “Science de la classification des êtres vivants, inventé par le botaniste Augustin Pyrame de Candolle”.

Si on reprend l'étymologie, l'origine du mot regroupe “Taxi” ( “Taxis en grec signifiant arrangement, ordre) et “nomie” (qui désigne les connaissances, le partage de savoir en grec ancien) ne laissent aucun doute quant à sa définition : c'est un mot pour exprimer le rangement en suivant une logique !

Bon, la minute culture est terminée, passons à la partie technique maintenant 🙂

Les pré-requis

Pour lister des choses, encore faut-il qu'elles existent : commencez par créer quelques pages avec leurs illustrations que vous aurez soigneusement sélectionné sur des sites d'images libres de droit.

Et en plus ça vous permettra de répéter quelques commandes utiles !

Créez 2 autres articles de blog en leur affectant 2 images différentes, et en les mettant dans la catégorie “blogging” par exemple.

Vous devriez désormais avoir 3 articles dans votre dossier /blog.

Le template list.html

Rendez-vous maintenant dans votre dossier layouts/_default/list.html

Nous avons déjà créé un template par défaut qui doit ressembler à ça :

{{ define "main" }}
<div class="container">
    <div class="section">
        <div class="content">
            <h1>{{ .Title }}</h1>
            {{ .Content }}
            <ul>
            {{ range .Pages }}
                <li><a href="{{ .Permalink }}">{{ .Title }}</a></li>
            {{ end }}
            </ul>
        </div>
    </div>
</div>
{{ end }}

Comme ce fichier se trouve dans le dossier _default, Hugo va récupérer ce template car aucun autre n'a été déterminé en priorité ; c'est ce que nous allons faire maintenant.

Allez maintenant dans le répertoire layout/blog : comme vous devez le deviner à ce stade, pour créer le template qui permet de lister les articles, il faut créer un fichier list.html dans ce dossier

Ajoutez ce contenu dans le fichier list.html :

{{ define "main" }}
<div class="container">
    <div class="section">
        <div class="content">
            <h1>{{ .Title }}</h1>
            {{ .Content }}
        </div>
    </div>
</div>
{{ end }}

Pour vous assurer que le template a bien été appliqué, rdv sur la page http://localhost:1313/blog/ : les liens vers les articles ne doivent plus apparaître désormais, car nous avons retiré la boucle

<ul>
    {{ range .Pages }}
    <li><a href="{{ .Permalink }}">{{ .Title }}</a></li>
    {{ end }}
</ul>

qui figure dans le template par défaut.

Comme pour le reste, nous allons utiliser Bulma pour créer une mise en page en 3 colonnes : ajoutez ceci après la div qui contient le titre de la page

<div class="columns is-multiline">
{{ range .Pages }}
    <div class="column is-third">
        <a href="{{ .Permalink }}">{{ .Title }}</a>
        <div class="content">
        {{ .Summary }}
        </div>
    </div>
{{ end }}
</div>

Côté public, votre page doit désormais afficher 3 colonnes avec le titre et un lien vers l'article, plus un résumé de son contenu :

Liste des articles

Bien sûr ces colonnes sont “responsives” : essayez de redimensionner votre navigateur pour un format “mobile”, et vous verrez les posts se chevaucher. Bref, pas besoin de revenir là dessus, et merci Bulma !

Améliorer l'affichage des articles en utilisant des partials

Bon, maintenant on va voir un truc pratique pour gérer l'intégration de nos articles : les partials.

Vous vous souvenez des partials ? On en a rapidement parlé au début : c'est une méthode, une fonction, qui permet de créer des mini-templates dans vos templates. On peut un peu comparer ça aux widgets ou modules que l'on rencontre dans d'autres CMS.

Bref, pour que ça soit plus concret, nous allons donner l'apparence de cartes à nos différents posts (en suivant la doc de Bulma à ce sujet : https://bulma.io/documentation/components/card/)

  1. Créez d'abord un dossier /partials/widgets
  2. Dans ce dossier /widgets, ajoutez le fichier post-card.html : comme son nom l'indique, il s'agira du partial qui permettra de structurer les posts d'une liste sous forme de cartes
  3. Dans ce fichier post-cards.html, ajoutez les lignes qui permettent d'appeler le titre et le résumé de l'article comme vous l'avez fait dans le template list.html
<a href="{{ .Permalink }}">{{ .Title }}</a>
<div class="content">
    {{ .Summary }}
</div>
  1. Ensuite, effacez ces lignes dans votre template list.html, et mettez ceci à la place :
{{ range .Pages }}
<div class="column is-third">
    {{ partial "widgets/post-card.html" . }}
</div>
{{ end }}
  1. Retournez sur votre page /blog : normalement, rien n'a changé, et c'est normal : c'est le contenu du partial qui a prit le relais ! Désormais, vous allez pouvoir travailler uniquement dans ce partial post-card.html pour gérer l'affichage des articles en liste.

Améliorons maintenant le contenu de cette carte, en ajoutant les éléments suivants :

  • Un cadre autour de chaque extrait
  • Une image d'illustration
  • Le nom de l'auteur et la date de publication
  • Des liens vers les catégories auxquelles sont attachés les articles
{{ $permalink := .Permalink }}
<div class="card">
    <div class="card-image">
        <figure class="image is-3by2">
            {{ with .Params.images }}
            <a href="{{ $permalink }}"><img src="{{ index . 0 }}" alt=""></a>
            {{ end }}
        </figure>
    </div>
    <div class="card-content">
        <a class="title is-4" href="{{ .Permalink }}">{{ .Title }}</a>
        <span class="heading">{{ .Site.Params.Author }} | <time>{{ .PublishDate.Format "January 2, 2006"}}</time></span>
        <div class="content">
            {{ .Summary }}
        </div>
        <div class="tags is-pulled-right">
            {{ range .Params.categories }}
            <a class="tag is-primary is-radiusless" href="/categories/{{ . | urlize}}">{{ . }}</a>
            {{ end }}
        </div>
    </div>
</div>

Quelques explications sur ce bout de code :

Je ne vais pas m'attarder sur le html et le css, il suffit de lire la doc du lien que j'ai donné juste avant (pour rappel : https://bulma.io/documentation/components/card/ )

On remarque une nouvelle forme de variable en toute première ligne :

{{ $permalink := .Permalink }}

Cette ligne permet d'affecter la valeur de .Permalink à une variable $permalink qui va nous servir pour récupérer le lien dans un contexte qui n'autorise pas l'appel direct de .Permalink.

Regardez un peu plus bas :

{{ with .Params.images }}
    <a href="{{ $permalink }}"><img src="{{ index . 0 }}" alt=""></a>
{{ end }}

Vous vous demanderez sûrement : pourquoi on ne peut pas mettre directement

<a href="{{ .Permalink }}">

sur notre image ?

En fait la réponse est assez simple quand on a comprit :on se retrouve dans un contexte relatif aux images : l'appel avec .Permalink ne peut donc pas fonctionner directement, car ce n'est pas un paramètre qui peut être utilisé dans la boucle .Params.images. C'est pourquoi il faut d'abord lui affecter une valeur en dehors de cette boucle avant de l'appeler.

Pour les développeurs, je pense que ça coule de source : tout est une question de contexte, et c'est logique : à aucun moment on n'affecte un lien à l'image, donc on ne peut pas récupérer cette info.

Pour les non-développeurs, dites-vous simplement que le lien vers un article et les paramètres d'une image sont indépendants, c'est pourquoi on ne peut pas appeler le paramètre “lien” d'une image ! (J'étofferai cette partie là si besoin un peu plus tard)

Pour le reste, c'est du classique : on appelle le titre, les méta-données, et la version résumée du contenu de chaque post.

La petite variante se trouve au niveau de la boucle finale qui permet d'afficher les catégories :

{{ range .Params.categories }}
    <a class="tag is-primary is-radiusless" href="/categories/{{ . | urlize}}">{{ . }}</a>
{{ end }}

Ici, on va récupérer les paramètres de catégorie tels que renseignés dans le front matter, et on va utiliser la fonction urlize pour créer une URL propre (cf la page sur la création des templates d'articles pour plus d'infos sur la fonction urlize)

La pagination

Au fil du temps, les articles du site s'accumulent, et ça va devenir compliqué de tout afficher sur la même page ; c'est là qu'intervient la fonction de pagination de Hugo.

Par défaut, Hugo affichera 10 articles à la fois, mais on peut changer ça très simplement dans le fichier config.toml. Si on veut par exemple n'afficher que 6 articles à la fois, il faudra ajouter cette ligne :

paginate = 6

Il faut ensuite ajouter l'appel de la fonction qui permet d'ajouter la pagination au template list.html :

à la ligne {{ range .Pages }} visible en bas du template

<div class="columns is-multiline">
    {{ range .Pages }}
    <div class="column is-third">
        {{ partial "widgets/post-card.html" . }}
    </div>
    {{ end }}
</div>

Ecrivez plutôt

{{ range .Paginator.Pages }}

Si vous changez maintenant le nombre d'éléments à afficher dans config.toml (par exemple en mettant le chiffre 2 à la place du 6 mis précédemment), vous ne verrez que 2 articles s'afficher sur cette page.

Pour en savoir plus sur cette fonction .Paginator je vous invite à consulter la page dédiée sur la documentation officielle : https://gohugo.io/templates/pagination/

Maintenant il va falloir ajouter des liens qui permettent de naviguer vers les autres pages : Hugo propose un partial clé en main qui permet d'appeler automatiquement l'énumération des pages. Intégrez ce bout de code après la dernière column et voyez ce qui apparait

{{ template "_internal/pagination.html" . }}

Ça devrait vous donner un truc dans le genre côté front :

Pagination

Pas super sexy comme présentation ; nous allons donc devoir charter cette zone.

Hugo propose un template prêt à l'emploi pour ce partial, que vous pouvez retrouver ici : https://github.com/gohugoio/hugo/blob/master/tpl/tplimpl/embedded/templates/pagination.html

Et voilà le code adapté avec le framework Bulma, à copier-coller dans le fichier partials/widgets/pagination.html :

{{ $pag := $.Paginator }}
{{ if gt $pag.TotalPages 1 }}
<nav class="pagination">
<ul class="pagination-list">
    {{ with $pag.First }}
    <li>
        <a href="{{ .URL }}" class="pagination-link" {{ if not $pag.HasPrev }} disabled{{ end }} aria-label="First"><span aria-hidden="true">&laquo;&laquo;</span></a>
    </li>
    {{ end }}
    <li>
        <a href="{{ if $pag.HasPrev }}{{ $pag.Prev.URL }}{{ end }}" class="pagination-link" {{ if not $pag.HasPrev }} disabled{{ end }} aria-label="Previous"><span aria-hidden="true">&laquo;</span></a>
    </li>
    {{ $ellipsed := false }}
    {{ $shouldEllipse := false }}
    {{ range $pag.Pagers }}
    {{ $right := sub .TotalPages .PageNumber }}
    {{ $showNumber := or (le .PageNumber 3) (eq $right 0) }}
    {{ $showNumber := or $showNumber (and (gt .PageNumber (sub $pag.PageNumber 2)) (lt .PageNumber (add $pag.PageNumber 2)))  }}
    {{ if $showNumber }}
        {{ $ellipsed = false }}
        {{ $shouldEllipse = false }}
    {{ else }}
        {{ $shouldEllipse = not $ellipsed }}
        {{ $ellipsed = true }}
    {{ end }}
    {{ if $showNumber }}
    <li><a class="pagination-link {{ if eq . $pag }}is-current{{ end }}" href="{{ .URL }}">{{ .PageNumber }}</a></li>
    {{ else if $shouldEllipse }}
    <li class="pagination-link" disabled><span aria-hidden="true">&nbsp;&hellip;&nbsp;</span></li>
    {{ end }}
    {{ end }}
    <li>
    <a href="{{ if $pag.HasNext }}{{ $pag.Next.URL }}{{ end }}" class="pagination-link" {{ if not $pag.HasNext }}disabled{{ end }} aria-label="Next"><span aria-hidden="true">&raquo;</span></a>
    </li>
    {{ with $pag.Last }}
    <li>
        <a href="{{ .URL }}" class="pagination-link" {{ if not $pag.HasNext }}disabled{{ end }} aria-label="Last"><span aria-hidden="true">&raquo;&raquo;</span></a>
    </li>
    {{ end }}
</ul>
</nav>
{{ end }}

Bien. Mais ce n'est pas tout ! Si vous regardez votre site côté front, vous verrez que rien n'a changé : c'est normal, on appel la version “clé en main” du template de pagination d'Hugo.

Il faut donc tout simplement remplacer l'appel du template par défaut de pagination par notre nouveau partial :

A la place de

{{ template "_internal/pagination.html" . }}

Mettez plutôt

{{ partial "widgets/pagination.html" . }}

Ensuite, retournez voir votre site côté front : la présentation est tout de suite plus sympa !

Pagination stylée

Pour centrer la pagination, il suffit de l'intégrer avec la classe Bulma “is-centered” :

<div class="columns is-centered">
    <div class="column is-narrow">
        {{ partial "widgets/pagination.html" . }}
    </div>
</div>

Ajouter une description à la catégorie

Vous aurez remarqué que le template de la page autorise l'utilisation des variables .Title et .Content ; en fait, ça permet tout simplement de gérer l'affichage du contenu et du titre de la page content/blog/_index.md ( le fichier markdown de la 1ere page qui s'affiche lorsqu'on se rend dans la partie blog)

Vous pouvez donc par exemple utiliser le contenu suivant :

---
title: "Blog"
date: 2020-04-06T11:00:27+02:00
---

## Titre de la page d'accueil de la rubrique "Blog"

Bonjour et bienvenue dans la catégorie "blog"

et voilà ce à quoi doit ressembler votre page in fine (à peu près, et si vous avez défini une structure en 3 colonnes) :

Mise en page d'une liste

Les catégories

Nous avons créé un template relatif à la première liste des articles, la page qui regroupe tous les contenus, toutes catégories confondues.

Maintenant, il va falloir créer le template des “sous-catégories”, qui peut différer quelque peu de la page d'accueil de /blog. En effet, à l'heure actuelle, si vous cliquez sur l'une des étiquettes (la catégorie “blogging” par exemple), la page affichée est vide.

En général, pour ne pas trop perturber les visiteurs, on a tendance à garder une structure uniforme sur toutes les pages ; on va donc garder cette affiche sous forme de “carte” (et ça nous arrange bien, il n'y aura qu'à reprendre la structure html/css qui a déjà été faite !)

Pour gérer l'affichage des pages de toutes les catégories, il faut créer le template layouts/categories/taxonomy.html

Voici le contenu du fichier, qui doit ressembler à quelques éléments près à ce que vous avez déjà fait :

{{ define "main" }}
<div class="container">
    <div class="section">
        <a href="/categories/">Liste des catégories</a>
        <div class="content">
            <h1>Category: {{ .Title }}</h1>
            {{ .Content }}
        </div>
        <div class="columns is-multiline">
            {{ range .Paginator.Pages }}
            <div class="column is-third">
                {{ partial "widgets/post-card.html" . }}
            </div>
            {{ end }}
        </div>
        <div class="columns is-centered">
            <div class="column is-narrow">
                {{ partial "widgets/pagination.html" . }}
            </div>
        </div>
    </div>
</div>
{{ end }}

Vous pouvez par la suite vérifier que le template fonctionne bien : regardez votre page “blogging” à nouveau - seuls les articles qui auront été rangés dans cette catégorie devraient apparaitre.

La page qui liste les catégories

Cliquez sur le lien “Liste des catégories” présent en haut de page : vous devriez reculer d'un cran dans l'arborescence, mais tomber sur une page vide. En effet, nous avons créer des pages qui liste des articles (ou des “posts”), mais pas celle qui permet de lister l'ensemble des catégories.

Nous allons remédier à ça en créant un template dédié à cet usage : le template layouts/categories/terms.html

{{ define "main" }}
<div class="container">
    <div class="section">
        <div class="content">
            <h1>{{ .Title }}</h1>
            {{ .Content }}
        </div>
        <div class="columns is-mobile is-multiline">
            {{ range .Data.Terms.ByCount }}
            <div class="column is-half-mobile is-one-third-tablet is-one-quarter-desktop is-one-fifth-widescreen">
                <div class="card">
                    <div class="card-image">
                        <a href="{{ .Page.Permalink }}">
                            <figure class="image is-3by2">
                                {{ $firstChild := index .Pages 0 }}
                                {{ with $firstChild.Params.images }}
                                <img src="{{ index . 0 }}" alt="">
                                {{ end }}
                            </figure>
                        </a>
                    </div>
                    <div class="card-content has-text-centered">
                        <div>
                            <a class="title is-5 is-size-6-mobile" href="{{ .Page.Permalink }}">{{ .Page.Title }}</a>
                            {{ $pageCount := len .Pages }}
                            <p>{{ $pageCount }} post{{ if ne $pageCount 1 }}s{{ end }} </p>
                        </div>
                    </div>
                </div>
            </div>
            {{ end }}
        </div>
    </div>
</div>
{{ end }}

Quelques explications s'imposent :

- Le début est relativement similaire à ce que vous avez déjà vu : on affiche le titre du fichier markdown, ainsi que son contenu en haut de la page

- Ensuite, dans une structure responsive qui va s'adapter en fonction de la résolution d'écran, les catégories vont s'afficher en 2, 3, 4 ou 5 colonnes (les différentes classes utilisées sont assez claire à comprendre)

- Cette structure responsive est conditionnée également par la boucle {{ range .Data.Terms.ByCount }} qui, comme l'indique la document à ce sujet (https://gohugo.io/variables/taxonomy/#taxonomy-terms-page-variables) , permet de classer les catégories en fonction du nombre d'articles qui y sont référencés.

- Notez qu'il est possible de choisir un classement alphabétique avec la fonction {{ range .Data.Terms.Alphabetical }} , et qu'il est également possible d'appliquer l'ordre inverse en ajoutant .Reverse à la fin ! (Donc en gros, si on veut par exemple classer dans l'ordre “anti-alphabétique”, il faut tout simplement écrire {{ range .Data.Terms.Alphabetical.Reverse }} - Ça marche aussi pour l'ordre inverse relatif au nombre d'articles référencés dans chaque catégorie )

- Ensuite, la catégorie va afficher l'image du 1er article qui y est publié grâce à la boucle suivante (revoir les explications sur les variables extérieures si vous avez du mal à comprendre ce bout de code) :

{{ $firstChild := index .Pages 0 }}
{{ with $firstChild.Params.images }}
<img src="{{ index . 0 }}" alt="">
{{ end }}

- Enfin, on va pouvoir récupérer le nombre d'articles grâce à la boucle suivante :

{{ $pageCount := len .Pages }}
<p>{{ $pageCount }} post{{ if ne $pageCount 1 }}s{{ end }} </p>

NB pour les développeurs - vous remarquerez des termes familiers - index et len, qui sont très souvent utilisés lorsqu'on utilise des tableaux dans d'autres langages. Dites-vous simplement que Hugo considère chaque liste d'article comme un tableau, et que lorsqu'on appelle index .Pages 0, ça signifie qu'on appelle le tout premier élément du tableau “Pages”, et lorsqu'on demande len .Pages, on veut juste connaître la taille totale du tableau ;-)

Comme je n'ai attaché mes articles qu'à 2 catégories, voilà à quoi ça ressemble chez moi maintenant :

Liste des catégories

Ajouter du contenu à cette page

Si vous voulez ajouter un peu de contenu à cette page de liste des catégories, rien de plus simple : il suffit de créer la page d'index du dossier /categories !

hugo new /categories/_index.md

Ajouter la liste des catégories au menu de navigation

Aller, dernier petit truc, on va ajouter cette page qui liste toutes les catégories dans notre menu : ajoutez simplement un item au menu principal dans votre fichier config.toml

[[menu.main]]
    name = "Categories"
    url = "/categories"

L'élément “Catégories” devrait maintenant apparaitre dans le menu principal de votre site !

Fin de la partie sur les taxonomies

Bon, ce n'était pas un petit morceau, mais maintenant vous devriez avoir une connaissance assez poussée du langage de templating de Hugo.

On a fait la plus grosse partie du travail, maintenant on va ajouter des trucs un peu plus funs !

Vous devriez également aimer ce qui suit...