Tworzenie prezentacji z reveal.js i Hugo

They may forget what you said, but they will never forget how you made them feel.
Prezentacja jako strona WWW
Po głębszym namyśle ku własnemu zaskoczeniu muszę przyznać, że Microsoft PowerPoint jako narzędzie do tworzenia prezentacji jest nie tyle prostacki, ile właśnie zbyt bogaty. Gdyby go uprościć i dodać funkcję ucinania rąk po wrzuceniu na slajd więcej niż pięćdziesięciu słów1, odmieniłby życie wielu z nas na lepsze, szczególnie w momentach, gdy występujemy w roli słuchaczy. Owszem, PowerPoint posiada bardzo ciekawe funkcje. Obecnie potrafi konwertować odręczne bazgroły na kształty, np. konwertuje kartofla na okrąg, choć czasem wychodzi z tego przekrzywiona elipsa, ale to już moja wina, trzeba było się bardziej starać. Wprawdzie jest to średnio przydatne, bo tak naprawdę na prezentacjach najczęściej chcemy dodawać strzałki, ale jak tylko nauczymy się rysować je bez odrywania ręki i odpowiednio równo, i tak, żeby grot był proporcjonalny do linii, to PowerPoint przerobi taką prawie idealną strzałkę na całkiem idealną. Przyda się tu doświadczenie w rysunku technicznym. O ile ćwiczyliśmy go przez dwa lata myszką w Paincie.
Jeżeli chcemy rysować równania matematyczne, program radzi sobie za to całkiem dobrze, choć pierwiastek kwadratowy wychodzi bordowy (sic!). To oczywiście nikomu nie przeszkadza, bo jeżeli ktoś potrzebuje wstawić dużo równań do prezentacji, to jest już entuzjastą krzyżówek hetmańskich, czyli użytkownikiem LaTeX-owego Beamera, który pozwala wyrazić się w ramach estetyki „schludnie, choć nasrane”. Docelowym formatem jest tu PDF, więc odkąd przeglądarki internetowe nauczyły się obsługiwać te pliki, można taką prezentację otworzyć na każdym komputerze. Co prawda bez animacji między slajdami możemy mieć wrażenie, że stoimy przed audytorium w płóciennym worku z drewniakami na stopach, ale przynajmniej nie wybierzemy jakiegoś żenującego przejścia, od którego ludzie dostaną choroby lokomocyjnej.
Oprócz edytorów WYSIWYG i szablonów LaTeX-owych jest jednak jeszcze trzecia droga — frameworki webowe, w których prezentacja ma postać strony WWW, niekoniecznie dostępnej on-line (choć takie rozwiązanie przedstawiam poniżej). Znanym i lubianym przykładem jest reveal.js. Zalecany sposób instalacji przewiduje wykorzystanie menedżera pakietów npm, można jednak po prostu sklonować repozytorium, użyć którejś z przykładowych prezentacji z boilerplatem w sekcji head
oraz body
i napisać slajdy z użyciem prostego HTML-a lub Markdowna. Jeżeli znamy trochę CSS, możemy łatwo dodać kilka wodotrysków w postaci animacji, kolorów, wykresów czy ciekawszego formatowania tekstu, zaś znajomość JavaScriptu daje nam właściwie nieograniczone możliwości.
Formatem docelowym jest strona WWW2, którą można wygodnie obsługiwać zarówno myszką, jak i klawiaturą. Pewną wadą jest fak, że kończymy z masą plików, które towarzyszą naszemu głównemu plikowi *.html
, ale właściwie, czym się różni skopiowanie na pendrive’a całego katalogu zamiast pojedynczego pliku *.pdf
czy *.pttx
? Otóż różnica polega na tym, że nie musimy niczego kopiować na pendrive’a, ale o tym za chwilę.
Wykorzystanie generatora stron statycznych
Skoro mamy prezentację w formie strony WWW, dlaczego by nie wrzucić jej po prostu do Internetu? Będzie wówczas dostępna na każdym komputerze podłączonym do Sieci. A gdyby wykorzystać do tego generator stron statycznych, np. Hugo? Otrzymamy wówczas nowe możliwości, szczególnie korzystne w przypadku przygotowywania cyklu prezentacji. Możemy łatwo użyć do każdej z nich tego samego zestawu CSS i konfiguracji reveal.js, zachowując zasadę DRY. Prawdziwym udogodnieniem jest jednak możliwość użycia systemu szablonów i zdefiniowanie własnych elementów, których używamy często na slajdach. Dzięki temu nawet fikuśne wynalazki, które pieczołowicie wysmarowaliśmy w HTML-u, możemy łatwo użyć powtórnie. A ponieważ mówimy o generatorze stron statycznych, otrzymamy na wyjściu pliki, które można zarówno hostować w Sieci, jak i skopiować na pendrive’a i otwierać lokalnie. Zatem do roboty!
Środowisko pracy z Hugo
Instalacja i konfiguracja
Zaczynamy od instalacji Hugo, który powinien być dostępny w większości popularnych dystrybucji (choć niekoniecznie w najnowszej wersji, bo rozwój tego programu jest bardzo dynamiczny). Następnie tworzymy katalog slides-site
, a w nim plik konfiguracyjny hugo.toml
. Nie używam komendy hugo new site
, bo nie będziemy potrzebować całej struktury katalogów.
W konfiguracji wyłączamy nieużywane sekcje strony, aby Hugo nie krzyczał na nas, że brakuje mu szablonów, które nam nie są potrzebne. Włączamy za to względne ścieżki do plików, dzięki czemu wygenerowane zasoby da się otworzyć w przeglądarce np. z pendrive’a.
disableKinds = ['home', 'taxonomy']
relativeURLs = true
Szablon
Tworzymy katalog layouts/_default
, a w nim plik single.html
, czyli najważniejszy plik w projekcie — główny szablon wszystkich prezentacji:
<!DOCTYPE html>
<html lang="pl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>{{ with .Title }}{{ . }}{{ else }}Prezentacja{{ end }}{{ with .Params.subtitle }}: {{ . }}{{ end }}</title>
<!-- Podstawowe style reveal.js. -->
<link rel="stylesheet" href="/reveal.js/dist/reset.css">
<link rel="stylesheet" href="/reveal.js/dist/reveal.css">
<link rel="stylesheet" href="/reveal.js/dist/theme/moon.css">
<!-- Kolorowanie składni. -->
<link rel="stylesheet" href="/reveal.js/plugin/highlight/monokai.css">
<!-- Skrypty reveal.js. -->
<script defer src="/reveal.js/dist/reveal.js"></script>
<script defer src="/reveal.js/plugin/markdown/markdown.js"></script>
<script defer src="/reveal.js/plugin/highlight/highlight.js"></script>
<script defer src="/reveal.js/plugin/notes/notes.js"></script>
<script defer src="/reveal.js/plugin/zoom/zoom.js"></script>
<script defer src="/reveal.js/plugin/search/search.js"></script>
<script defer src="/reveal.js/plugin/math/math.js"></script>
<!-- Konfiguracja. -->
<script defer src="/scripts/configuration.js"></script>
<!-- Własne style. -->
{{ with resources.Get "styles/main.sass" | toCSS -}}
<link rel="stylesheet" href="{{ .RelPermalink }}">
{{- end }}
</head>
<body>
<div class="reveal"><div class="slides">
{{ with .Title }}
<!-- Automatyczny slajd tytułowy. -->
<section>
<h1>{{ . }}{{ with $.Params.subtitle }}:<br>{{ . }}{{ end }}</h1>
<p>
Mariusz Chilmon<br>
<a href="mailto:vmario@vmario.org">vmario@vmario.org</a><br>
<a href="https://www.vmario.org">www.vmario.org</a>
</p>
</section>
{{ end }}
<!-- Reszta prezentacji. -->
{{ .Content }}
</div></div>
</body>
</html>
Oprócz boilerplate’a reveal.js zawiera on kilka ulepszeń (podświetlone linie):
- skrypt z konfiguracją;
- własny arkusz stylów;
- automatycznie generowany slajd tytułowy, wspólny dla wszystkich prezentacji;
- miejsce na treść, którą Hugo wstawi z oddzielnego pliku.
Tutaj można też wstawić pluginy. Polecam chalkboard, który warto dodać do prezentacji, jeżeli będziemy ją przedstawiać, mając dostęp do myszki — umożliwi nam rysowanie po slajdach.
Skrypty i style
Skrypt konfigurujący reveal.js i pluginy zapisujemy w pliku static/scripts/configuration.js
:
Reveal.initialize({
hash: true,
transition: 'convex',
backgroundTransition: 'convex',
mouseWheel: true,
plugins: [RevealMarkdown, RevealHighlight, RevealNotes, RevealZoom, RevealSearch, RevealMath.KaTeX]
});
Style umieszczamy w assets/styles/main.sass
. Użyłem ich do zwiększenia wysokości bloku kodu na slajdzie:
.reveal pre code
max-height: 600px
Tu widać korzyść z użycia Hugo — możemy wspierać się preprocesorem Sass, dzięki czemu pisanie większych arkuszy stylów staje się całkiem znośne.
Shortcodes
Teraz coś jeszcze bardziej przydatnego — shortcodes, czyli swego rodzaju snippety, które umożliwią łatwe wstawienie powtarzających się elementów na stronę, czy raczej — w naszym przypadku — prezentację. Pliki umieszczamy w layouts/shortcodes
.
Nowa część prezentacji z nagłówkiem poziomu h2
(part.html
):
<section>
<h2>{{ .Get 0 | safeHTML }}</h2>
</section>
Standardowy slajd z nagłówkiem poziomu h3
(slide.html
):
<section>
{{ with .Get 0 }}<h3>{{ . | safeHTML }}</h3>{{ end }}
{{ .Inner }}
</section>
Slajd z listą wypunktowaną, która będzie stopniowo odsłaniania (ul-slide.html
):
<section>
<h3>{{ .Get 0 | safeHTML }}</h3>
<ul>
{{ .Inner }}
</ul>
</section>
Element listy wypunktowanej (lf.html
):
<li class="fragment">
I najbardziej zaawansowany shortcode — listing z płynnymi przejściami między wskazanymi fragmentami, i możliwością pobrania treści z zewnętrznego pliku (code.html
):
<pre><code
data-trim data-noescape data-id="code-animation"
{{ with .Get "lang" }}class="lang-{{ . }}"{{ end }}
{{ with .Get "lines" }}data-line-numbers="{{ . }}"{{ end }}
>
{{- with .Get "file" -}}
{{ (path.Join $.Page.File.Dir .) | readFile }}
{{ else }}
{{- .Inner -}}
{{ end }}
</code></pre>

Listing z kolorowaniem składni i podświetleniem fragmentu kodu
Dodanie reveal.js
W katalogu static/reveal.js
umieszczamy kod reveal.js, rozpakowując tam pobrane archiwum z najnowszą wersją, klonując repozytorium czy korzystając z submodułów Git.
Środowisko pracy jest już gotowe.
Wydanie i publikacja
Przykładowa prezentacja
Treść prezentacji umieszczamy w content/slides-hello/index.html
wraz z jakimś kawałkiem kodu example.py
.
Dodajemy dwa slajdy z treścią i dwa z tytułami części prezentacji. Slajd tytułowy zostanie wygenerowany automatycznie na podstawie metadanych opisanych w YAML-u i oddzielonych od treści znacznikami (---
):
---
title: "Prezentacja"
subtitle: "Taka testowa"
---
{{< part "Wstęp" >}}
{{< ul-slide "Slajd z listą" >}}
{{< lf >}}raz
{{< lf >}}<em>dwa</em>
{{< lf >}}<strong>trzy</strong>
{{< /ul-slide >}}
{{< part "Przykład" >}}
{{< slide >}}
{{< code lines="1|3-4|6-17|19-26" lang="python" file="example.py" />}}
{{< /slide >}}

Slajd tytułowy generowany z metadanych
Przykładowy kod w Pythonie nie ma większego znaczenia i służy tylko zaprezentowaniu możliwości wyświetlania listingów i pokazaniu, że omawiany kod można trzymać jako samodzielny plik, który można uruchamiać, testować i poprawiać, a zmodyfikowana zawartość będzie automatycznie aktualizowana na prezentacji, zgodnie z zasadą DRY.
#!/usr/bin/env python3
import argparse
import logging
def configureLogging():
"""Konfiguruje log."""
parser = argparse.ArgumentParser()
parser.add_argument('-l', '--log', default='INFO')
args = parser.parse_args()
debugLevel = getattr(logging, args.log.upper(), None)
if not isinstance(debugLevel, int):
raise ValueError(f'Invalid log level: {debugLevel}')
logging.basicConfig(
format='%(asctime)s %(name)s:%(levelname)s:%(message)s',
datefmt='%H:%M:%S',
level=debugLevel)
def main():
"""Uruchamia przykład logowania."""
configureLogging()
logging.debug('debug')
logging.info('info')
logging.warning('warning')
logging.error('error')
logging.critical('critical')
if __name__ == "__main__":
main()
Wygenerowanie treści
Po uruchomieniu serwera Hugo:
hugo server
możemy obejrzeć prezentację w przeglądarce pod adresem http://localhost:1313/slides-test. Należy zwrócić uwagę, że bezpośrednio pod adresem http://localhost:1313 nie mamy nic, gdyż nie stworzyliśmy strony głównej. Możemy ją dodać, jeżeli potrzebujemy indeksu prezentacji dla własnej wygody lub w celu publikacji większej liczby prezentacji w Sieci.
Publikacja
Jeżeli prezentacja wygląda tak, jak chcemy, generujemy wersję do publikacji za pomocą:
hugo
i kopiujemy katalog public
na pendrive’a3. Do pokazu będziemy potrzebowali tylko przeglądarki internetowej. Sam dostęp do Sieci nie będzie nam potrzebny.
Jeżeli chcemy przesłać nasze działo e-mailem, możemy je spakować np. 7-Zip-em:
7z a slides.7z public/
Owszem, nie jest to tak zgrabne jak PDF, ale w zasadzie, po co komuś wysyłać prezentację, którą możemy opublikować on-line? W tym celu wystarczy np. wrzucić kod na GitHuba, a potem uruchomić hosting na serwerze Netlify, który obsługuje Hugo i sam będzie budował nasze prezentacje. Wszystko za darmo.
Archiwum z przykładem
Dla zainteresowanych załączam archiwum slides-site.tar.xz ze źródłami i zbudowaną prezentacją.
Podsumowanie
Mnie powyższe rozwiązanie sprawdziło się bardzo dobrze. Prezentacji dostępnej w sieci nie da się zgubić, za to bardzo łatwo udostępnić ją zainteresowanym. Problem może się pojawić tylko, jeśli komputer, z którego będziemy prowadzić wykład czy szkolenie, nie będzie miał połączenia z Internetem. Wydaje mi się jednak, że wraz z upływem lat prawdopodobieństwo braku Internetu spada, prawdopodobieństwo problemu z rzutnikiem pozostaje stałe, a krzywe te chyba już się przecięły na korzyść połączenia z Siecią.
-
Po przekroczeniu trzydziestu powinien być kontrolny kopniak w tyłek. I należałoby to zapisać w jakiejś międzynarodowej konwencji. ↩︎
-
Istnieje sposób na eksport prezentacji do PDF-a, ale jest to rozwiązanie trikowe, na dodatek pozbawiające nas przejść między slajdami, i wydaje mi się ostatecznością, po którą musimy sięgnąć, jeżeli mamy obowiązek dostarczyć slajdy w postaci papierowej. ↩︎
-
W praktyce różnica między zasobami wygenerowanymi przez
hugo
ihugo server
jest w omawianym przypadku nieistotna i sprowadza się głównie do skryptu odświeżającego stronę po edycji plików źródłowych, który jest wstrzykiwany przezhugo server
, a który nie jest potrzebny po zakończeniu pracy nad treścią. ↩︎