Blog

Hugo Tutorial PL. Odsłona piąta.

Ostatnia odsłona kursu poświęconego Hugo. Tym samym ostro przyśpieszamy, bo sporo zostało jeszcze do zrobienia.

Hugo Tutorial PL. Odsłona piąta.

Piąta i zarazem ostatnia odsłona tego wprowadzenia, co chciałbym od razu zaznaczyć. Nie znaczy to, że definitywnie żegnam się na moim blogu z Hugo, bynajmniej. Po prostu dzisiejszym wpisem chciałbym zakończyć dotychczasowy cykl, który miał nas doprowadzić do gotowej witryny o blogowym zacięciu. Niestety sporo zostało jeszcze do zrobienia, a ja właśnie zaczynam zasłużony urlop, więc przesadnie dużo czasu nie chcę temu tekstowi poświęcać, bo są sprawy ważne i ważniejsze. W efekcie będzie dzisiaj jeszcze więcej uproszczeń niż dotychczas, a przynajmniej więcej niż bym sobie życzył. Natomiast to mój blog, moje zasady, a ponieważ na urodzaj czytających narzekać nie mogę, to tym razem taka chałtura nie będzie mi specjalnie doskwierać.

Dzisiejszy wpis będzie składał się z 3 części. W pierwszej omówię zgrubnie filozofię “interakcji” między treścią a szablonami i pod tym względem kątem te ostatnie. W drugiej części punkt ciężkości nieco bardziej przeniesienie się na kontent i w tej sekcji dzisiejszych “ćwiczeń” zadbamy, by każda strona wyświetlała właściwy dla niej obrazek tła w nagłówku oraz stosowny do treści tytuł i podtytuł. W trzeciej części stworzymy wreszcie szablon dla tak zwanych list, czyli w naszym wypadku ten, który będzie odpowiadał za wyświetlanie zestawienia naszych wpisów blogowych, więc siłą rzeczy trzeba będzie krótko wspomnieć o pewnych mechanizmach dostępnych w języku szablonów, z którym w ramach Hugo “kooperujemy”.

Wszystko zaczyna się od kropki

Poniekąd to prawda, ale trzeba pamiętać, że otoczona jest ona dobrze nam znanymi nawiasami klamrowymi. Tym niemniej jeśli Hugo napotka znak kropki zaraz po ich otwarciu wówczas będzie wiedział, że w tym wypadku powinien wstawić jakąś oczekiwaną przez nas wartość dla tej konkretnej strony. Zazwyczaj będzie to coś znajdującego się odpowiednim pliku Markdown (dalej: MD), ale to nie jedyne źródło, z którego możemy dynamicznie pobierać stosowną zawartość. Tym niemniej podstawowe zmienne (variables), które wykorzystuje się w Hugo sprowadzają się do tych przynależnych stronie (Page Variables) oraz dla całej witryny (Site Variables). Te pierwsze - jak się łatwo domyśleć - odwołują się do danych z pliku MD, drugie do tego, co znajduje się w pliku config.toml.

Wskazanie źródła danych jest banalne i opiera się ono na tym, co wstawimy bezpośrednio po kropce. Jeśli będzie to nazwa interesującej nas zmiennej (np. znaną już nam zmienną Content) wówczas Hugo sięgnie do pliku MD. Natomiast gdy na początku (po kropce) dodamy Site, po której wstawimy kolejną kropkę i nazwę oczekiwanej zmiennej, wówczas Hugo będzie wiedział, że tym razem idzie o dane witryny.

Dla jednych i drugi twórcy Hugo przewidzieli całkiem sporo (jak bym powiedział: stanowczo za dużo) wbudowanych zmiennych, ale oczywiście jest też opcja tworzenia własnych, ale o tym trochę opowiem w drugiej części. A ponieważ jedną taką zmienną dla strony już poznaliśmy, to skupmy się na tych właściwych dla całej witryny. W tym celu otwórzmy plik config.toml.

baseURL = "http://example.org/"
languageCode = "en-us"
title = "My New Hugo Site"
theme = "cb-one"

Na dzień dobry mieliśmy utworzone tylko 3 zmienne, zaś tę czwartą i ostatnią w kolejności dodaliśmy sami. Skupmy się na dwóch z nich i w ramach ćwiczenia zmieńmy wartość languageCode na “pl”, zaś dla title wpiszmy na ten przykład “Mój fantastyczny blog”. Oczywiście zmiany w zapiszmy.

Teraz otwieramy plik baseof.html i nieco zmodyfikujemy drugi wiersz, tak by wyglądał on następująco:

<html lang={{ .Site.LanguageCode }} >

a następnie na warsztat bierzemy plik head.html i zmieniamy atrybut title, by wyglądał tak:

<title>{{.Site.Title}}</title>

Akurat ta zmiana będzie od razu widoczna w postaci nowej nazwy, która wyświetli się w zakładce otwartej dla naszej stronie (pod warunkiem, że wcześniej uruchomiliśmy serwer Hugo rzecz jasna).

Jak już wspomniałem w Hugo jest znacznie więcej gotowych zmiennych, tak dla poszczególnych stron jak i witryny, ale tutaj z grubsza chodziło o zaprezentowanie pewnej zasady, która leży u podstaw każdego udanego (efektywnego) tematu dla naszego projektu. Ich pełną listę można znaleźć w dokumentacji dla Hugo. Napomknąłem jednak, że nie jesteśmy na nie skazani, ponieważ możemy tworzyć własne zmienne. Tym zajmiemy się w drugiej części.

Autorskie podejście do zmiennych

Gotowych zmiennych w Hugo jest naprawdę sporo, zwłaszcza w przypadku tych dedykowanych stronom. Zapewne w wielu wypadkach bez problemu zaspokoją nasze potrzeby, ale na szczęście twórcy Hugo nie zamykają nas w klatce predefiniowanych rozwiązań i w tej części pokażę jak można wykreować własne zmienne w przypadku stron, czyli plików MD. Dla samej witryny (Site) zasada jest bardzo podobna, choć całość odrobinę się różni, więc na dzisiaj pominę tą opcję i skupię się na zawartości stron.

Sama zasada tworzenia i odwoływania się do nowo utworzonych zmiennych jest bardzo prosta, ale zanim do tego przejdziemy słów kilka na temat tego jak zbudowany jest plik MD, który stanowi źródło tych danych. W tym celu warto otworzyć którykolwiek z wcześniej spreparowanych plików tego typu. Wówczas u góry znajdziemy sekcję wydzieloną znakami myślników, która nas będzie w tym przypadku szczególnie interesować, ponieważ znajdują się tam - no powiedzmy - metadane naszej strony, a jednocześnie źródło większości zmiennych, z których możemy czerpać. Domyślny szablon nie jest przesadnie bogaty, ponieważ zawiera tytuł (domyślnie zgodny z nazwą pliku), datę utworzenia oraz informację o statusie strony. Nic oczywiście nie stoi na przeszkodzie, by dodać tam kolejne z predefiniowanej przez twórców Hugo listy zmiennych, ale my teraz dodamy własne i to takie, które pozwolą nam na “prawidłowe” wyświetlanie nagłówka (header), czyli dostosowanego do konkretnej strony. Nie jest to może najlepsze, a z pewnością nie jedyne rozwiązanie tego problemu, ale dzięki temu mogę zaprezentować kilka idei.

Otwieramy plik _index.md w katalogu “content” i dodamy w niej całą “sekcję”, która będzie odpowiadała za to jak finalnie będzie prezentował się nagłówek. Całość powinna wyglądać następująco (zwracam uwagę na wcięcie):

---
title: "Home"
date: 2021-07-20T16:53:03+02:00
draft: false
banner:
    headline: Home
    subline: A Blog Theme by Start Bootstrap
    background: assets/img/home-bg.jpg
---

Teraz wystarczy odpowiednio zmodyfikować plik header.html, w którym dodamy te nowe zmienne, tak aby wartości były dodawane dynamiczne do szablonu z plików MD. Po zmianach część dotycząca banera tytułowego powinna wyglądać następująco.

<!-- Page Header-->
<header class="masthead" style="background-image: url('{{ .Params.banner.background | relURL}}')">
   <div class="container position-relative px-4 px-lg-5">
       <div class="row gx-4 gx-lg-5 justify-content-center">
           <div class="col-md-10 col-lg-8 col-xl-7">
               <div class="site-heading">
                   <h1> {{ .Params.banner.headline }} </h1>
                   <span class="subheading"> {{ .Params.banner.subline }}</span>
               </div>
           </div>
       </div>
   </div>
</header>

Jak widać odwołaliśmy się do naszych zmiennych, przy czym każde takie wskazanie musieliśmy poprzedzić słowem “Params”, które w przypadku własnych zmiennych musi poprzedzać takie odwołanie. Ponadto ponieważ nasze właściwe zmienne (“headline”,   “subline” oraz “background”) były zagnieżdżone w sekcji “banner”, poprzedzić je musieliśmy jeszcze słowem “banner”.

Dodajmy jeszcze w pliku index.md z katalogu content/about odpowiednie wpisy, by otrzymać coś takiego:

---
title: "About"
date: 2021-07-20T16:53:30+02:00
draft: false
banner:
   headline: About Me
   subline: This is what I do.
   background: assets/img/about-bg.jpg
---

Tym sposobem strona About będzie wyglądała tak jak sobie to pierwotnie założyliśmy. Na koniec otwórzmy jeszcze plik 1-wpis.md i zmodyfukujmy go, by finalnie otrzymać coś takiego:

---
title: "1 Wpis"
date: 2021-07-25T15:56:27+02:00
description:
draft: false
banner:
   headline: Blog
   subline: Let's read it and find out what's in there.
   background: assets/img/contact-bg.jpg
postExcerpt: Problems look mighty small from 150 miles up
postTitle: Man must explore, and this is exploration at its greatest
---
 
## Man must explore, and this is exploration at its greatest
 
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam risus elit, accumsan et consectetur a, gravida et ante. Pellentesque pharetra non mi in commodo. Morbi ullamcorper molestie malesuada. Donec vel tincidunt eros, in auctor tortor. Vivamus facilisis enim ac dolor hendrerit, vel porta eros lacinia. Mauris dictum dignissim orci, vitae consequat enim finibus id. Sed eros est, maximus quis enim a, venenatis accumsan massa. Nullam vulputate nibh at nisi elementum, at rhoncus turpis imperdiet. Nam auctors purus id diam porta ullamcorper. Aliquam volutpat mollis fringilla. Quisque sodales porttitor nisl, in euismod orci pharetra a.
 
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam risus elit, accumsan et consectetur a, gravida et ante. Pellentesque pharetra non mi in commodo. Morbi ullamcorper molestie malesuada. Donec vel tincidunt eros, in auctor tortor. Vivamus facilisis enim ac dolor hendrerit, vel porta eros lacinia. Mauris dictum dignissim orci, vitae consequat enim finibus id. Sed eros est, maximus quis enim a, venenatis accumsan massa. Nullam vulputate nibh at nisi elementum, at rhoncus turpis imperdiet. Nam auctor purus id diam porta ullamcorper. Aliquam volutpat mollis fringilla. Quisque sodales porttitor nisl, in euismod orci pharetra a.

Te dwie dodatkowe zmienne (“postExcerpt” oraz “postTitle”) nie przydadzą się nam w przypadku wyświetlania zawartości posta, ale będą istotne dla trzeciej części dzisiejszego wpisu, ponieważ w przypadku strony Blog, która będzie zawierać listę naszych wpisów, wykorzystamy tę zmienną.

Listy, czyli finiszujemy

Tym razem bez zbędnego kombinowania. Otwieramy plik list.html i wklejamy następujący kawałek kodu:

{{ define "main" }}
 
 
       <!-- Main Content-->
 
       <div class="container px-4 px-lg-5">
           <div class="row gx-4 gx-lg-5 justify-content-center">
               <div class="col-md-10 col-lg-8 col-xl-7">
                   <!-- Post preview-->
                   {{ range .Pages }}
                   <div class="post-preview">
                       <a href="{{ .Permalink }}">
                           <h2 class="post-title"> {{ .Params.postTitle}} </h2>
                           <h3 class="post-subtitle">{{ .Params.postExcerpt }}</h3>
                       </a>
                       <p class="post-meta">
                           Posted by
                           <a href="#!">Start Bootstrap</a>
                           on September 24, 2021
                       </p>
                   </div>
                   <!-- Divider-->
                   <hr class="my-4" />
                   {{end}}
 
                   <!-- Pager-->
                   <div class="d-flex justify-content-end mb-4"><a class="btn btn-primary text-uppercase" href="#!">Older Posts →</a></div>
               </div>
           </div>
       </div>
 
{{ end }}

To co wymaga wyjaśnienia w tym przypadku to fragment znajdujący się między znacznikami {{ range .Pages }} oraz {{end}}, która znajduje się wewnątrz sekcji main, bo właśnie tam dzieje się cała magia.

Otóż słowo range oznacza instrukcję wykonania pętli przez zadaną listę jakiś wartości. Nie jest to polecenie właściwie dla Hugo, ale część wyposażenia, które dostępne jest dla języka znaczników dostarczanego w standardowej bibliotece języka Go. Wprawdzie nie jest to w tym momencie najważniejsze, jednak chodzi mi o podkreślenie faktu, że dzięki takiemu powinowactwu użytkownik Hugo dysponuje narzędziami, które pozwalają na realizację typowych zadań związanych ze sterowaniem przepływem danych.

Niestety z przyczyn, o których pisałem na samym początku, nie jestem w stanie tego wątku w niniejszym tekście rozwinąć nawet w minimalnym stopniu, ale podejrzewam, że w przyszłości będę miał okazję do tego wrócić. Natomiast kto będzie chciał - do czego namawiam - bez większego problemu może doczytać sobie więcej na ten temat.

W tym momencie chodziło głównie o to, by pokazać w jaki sposób będziemy mogli wyświetlić listę wszystkich wpisów blogowych, przy czym polecenie range nie dostarcza samej treści, a jedynie pozwala ją przetworzyć w odpowiedniej pętli. Tutaj mamy do czynienia z najprostszą możliwą formą iteracji, ponieważ nie wskazaliśmy żadnych dodatkowych warunków do iteracji, czyli mówiąc wprost: pętla zakończy działanie jak przebiegnie po wszystkich wpisach. Dlatego drugim komponentem, który umożliwia nam utworzenie listy takich postów, jest zmiana “Pages”. Ona to pozwala nam na dostęp do wszystkich treści, jakie znajdują się w zasięgu kontekstu danej strony. Mówiąc bardziej po ludzku: ponieważ w katalogu /content/blog mamy do czynienia z plikiem _index.md (istotne jest podkreślenie, od którego zaczyna się nazwa tego pliku), to zmienna “Pages” daje nam dostęp do wszystkich stron (plików MD) umieszczonych w tym katalogu, jak i w ewentualnych podkatalogach. Jeśli ktoś chciałby poszerzyć wiedzę w tym zakresie może poczytać o dwóch rodzajach “pakietów stron” (page bundle).

Zakończenie

Tym sposobem dysponujemy już podstawowym zakresem wiedzy, który pozwoli na stworzenie własnego motywu od podstaw czy raczej na bazie gotowych tematów w HTML. Choć z całą pewnością jest to raczej absolutne minimum i warto nieco poszerzyć własne horyzonty w tym zakresie, chociażby próbując przeniknąć działanie motywów przygotowanych przez innych, których nie brakuje na Githubie chociażby.

Niestety osobiście muszę przyznać, że właśnie kończący się cykl uważam za najbardziej nieudane przedsięwzięcie w mojej niezbyt okazałej historii na tym blogu. Niestety dopiero w połowie trzeciego wpisu zorientowałem się, że koncepcja prezentacji tematu, którą przyjąłem na samym początku, jest - w najlepszym razie - niespecjalnie optymalna. Niestety poza złożeniem samokrytyki w tym momencie nie mam na razie sił i przede wszystkim ochoty, by zrobić to jak należy. Co więcej temat Hugo trochę mnie się przejadł, a przede wszystkim odciągną na dłuższy czas od zgłębiania tematów, które mnie naprawdę obecnie interesują, a więc przede wszystkim zaprzyjaźnienia się z Golang. Obiecuję, że nie zostawię całkiem zupełnie tej kwestii i zapewne w jakieś niespecjalnie odległej przyszłości przygotuję porządnego tutka dla Hugo, ale raczej w formie bardziej multimedialnej, bo w końcu trzeba coś na ten mój nieszczęsny kanał YouTube wrzucić.