Tworzenie prezentacji z reveal.js i Hugo

8 minute read
Ozdobnik

They may forget what you said, but they will never forget how you made them feel.

Carl W. Buechner

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ą.


  1. Po przekroczeniu trzydziestu powinien być kontrolny kopniak w tyłek. I należałoby to zapisać w jakiejś międzynarodowej konwencji. ↩︎

  2. 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. ↩︎

  3. W praktyce różnica między zasobami wygenerowanymi przez hugohugo 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 przez hugo server, a który nie jest potrzebny po zakończeniu pracy nad treścią. ↩︎