Presentation slides. They can be a great tool for communicating ideas to others. I was looking for a way to create slide decks where I can specify the content as plain text and have it separate to the rendering. I’m sure this is somehow possible with PowerPoint or OpenOffice, but I didn’t bother to look for it. This post shows how I specify the content in a Markdown file and let it render by Reveal.js which I packed into a Docker image.
Updated on Mar 19, 2018
Mistakes were made. By me. Big time. Docker is NOT needed to render
the Markdown file later. I’ve misinterpreted the README of Reveal.js and
didn’t double-check it. What you really need is a web server, which
means you can do $ python -m SimpleHTTPServer
locally (in the
directory where your index.html
is) or use GitHub Pages [4]
without any additional work. I’ll keep this section to not change the
history, but be aware that it is not needed. Thanks to Chris H. for
pointing that out.
Reveal.js is capable of using Markdown files [1] and render them as HTML5 slides. The downside of Reveal.js is, that it needs a lot of things installed to work and I didn’t want to have that on my laptop simply to show fancy slides. So I decided to encapsulate it with Docker.
Create a file named Dockerfile
to specify the image’s content:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | FROM ubuntu:16.04
RUN apt-get update && apt-get install -y \
git \
nodejs \
nodejs-legacy \
npm \
&& rm -rf /var/lib/apt/lists/*
RUN git clone https://github.com/hakimel/reveal.js.git
RUN cd reveal.js/ && npm install
ADD ./index.html /reveal.js/index.html
EXPOSE 8000
ENTRYPOINT ["npm", "start", "--prefix", "/reveal.js/"]
|
nodejs
Reveal.js is a node.js application, that’s why we need it.
If you don’t install nodejs-legacy
, you’ll see this error message:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | [...]
sh: 1: node: not found
[...]
npm ERR! Linux 3.10.0-693.17.1.el7.x86_64
npm ERR! argv "/usr/bin/nodejs" "/usr/bin/npm" "install"
npm ERR! node v4.2.6
npm ERR! npm v3.5.2
npm ERR! file sh
npm ERR! code ELIFECYCLE
npm ERR! errno ENOENT
npm ERR! syscall spawn
npm ERR! node-sass@4.7.2 install: `node scripts/install.js`
npm ERR! spawn ENOENT
npm ERR!
npm ERR! Failed at the node-sass@4.7.2 install script 'node scripts/install.js'.
npm ERR! Make sure you have the latest version of node.js and npm installed.
|
npm
is the package manager for node.js applications, which is needed
so that the dependencies of Reveal.js get installed. The index.html
file
gets explained in the next section. The ENTRYPOINT
starts the web server
of Reveal.js when a container based on that image is started.
Create the file index.html
, which will be used by
reveal.js to serve when the HTTP server starts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | <!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Markus Slides</title>
<link rel="stylesheet" href="css/reveal.css">
<link rel="stylesheet" href="css/theme/black.css">
<!-- Theme used for syntax highlighting of code -->
<link rel="stylesheet" href="lib/css/zenburn.css">
<!-- Printing and PDF exports -->
<script>
var link = document.createElement( 'link' );
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = window.location.search.match( /print-pdf/gi ) ? 'css/print/pdf.css' : 'css/print/paper.css';
document.getElementsByTagName( 'head' )[0].appendChild( link );
</script>
</head>
<body>
<div class="reveal">
<div class="slides">
<section data-markdown="content/index.md"
data-separator="^\n\n\n"
data-separator-vertical="^\n\n"
data-separator-notes="^Note:"
data-charset="iso-8859-15">
</section>
</div>
</div>
<script src="lib/js/head.min.js"></script>
<script src="js/reveal.js"></script>
<script>
// More info about config & dependencies:
// - https://github.com/hakimel/reveal.js#configuration
// - https://github.com/hakimel/reveal.js#dependencies
Reveal.initialize({
dependencies: [
{ src: 'plugin/markdown/marked.js' },
{ src: 'plugin/markdown/markdown.js' },
{ src: 'plugin/notes/notes.js', async: true },
{ src: 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } }
]
});
</script>
</body>
</html>
|
I basically copied the index.html
which comes with Reveal.js and changed
only the highlighted lines.
Note
Please note that the links are URIs and not operating system file paths.
In detail:
data-markdown="content/index.md
specifies the path to the Markdown
file. This will become important later when we start the container and
do a bind mount from the host file system.data-separator="^\n\n\n"
tells Reveal.js to interpret 3 empty lines in
the index.md
content file as the beginning of a new slide.data-separator-vertical="^\n\n"
creates a new vertical slide,
which is a nice feature of Reveal.js, which, for example, let’s you add
backup slides closer to the content.data-separator-notes="^Note:"
let’s you add speaker notes which
get shown in a separate browser window when opened.data-charset="iso-8859-15"
make it work for Western European
languages.Let’s create the content linked to with content/index.md
in the next
section.
Create the file index.md
which specifies our content:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | # Example with Code
first slide
## Python Code
second slide
```python
def do_something():
action = "did something"
print(action)
return 0
```
Note:
This will only display in the notes window.
## Bullet List
third slide
* this
* and that
* and other things
# Image
fourth slide

|
As specified with ^\n\n\n"
, 3 empty lines signal
the beginning of a new slide. The speaker notes get added with Note:
.
The last highlighted lines links to an image. This example image
example.drawio.svg
(not shown in this post) is locally in the
very same directory where example.md
resides in. The link begins
with content/
though, because this the bind mount directory I
will declare when running the container later.
We have the Dockerfile and all things which need to be part of the image specified, now we need to build the image.
Create the file build.sh
to build the image. This needs to be executed
in the same directory as the Dockerfile:
1 2 3 | #!/usr/bin/env bash
docker build --rm --tag markus:revealjs .
|
I tagged the image with markus:revealjs
to reference it easier later on.
Build the image with:
1 | $ ./build.sh
|
Note
You don’t need to save those commands in bash scripts like I did here. It’s simply a thing I do when I have a hard time to remember the exact CLI command. It’s other benefit is, that I can commit that file into git (or any other VCS).
Create the file run.sh
to run the Docker container. The different
options are explained below this:
1 2 3 4 5 6 7 8 | #!/usr/bin/env bash
docker run \
-d \
-v $(pwd):/reveal.js/content:Z \
-p 50000:8000 \
--name slides \
markus:revealjs
|
docker run
: run a Docker image.-d
: let it run in the background as a daemon. I had trouble exiting
the process when running it as foreground process, that’s why I let it
run in the background.-v $(pwd):/reveal.js/content:Z
: do a bind mount of the current
directory on the host ($(pwd)
) to the directory /reveal.js/content
within the running container and consider the SELinux context (Z
)
so that Reveal.js is allowed to read the files in that directory.-p 50000:8000
: use the container’s exposed port 8000
and bind it
the the hosts port 50000
(I’ve arbitrarily chosen).--name slides
: gives the container the name slides
, which makes
it easier to reference to.markus:revealjs
: use the image with that tag (we’ve set when
building the image in the previous section).Run the container and watch your slides with:
1 2 | $ ./run.sh
$ google-chrome localhost:50000
|
This will open the index.html
which renders your Markdown content
with Reveal.js. The slide overview is opened with Esc
.
After everyone is thoroughly impressed by your slides, the container needs to be stopped and removed.
Create the file clean.sh
which helps with the cleanup:
1 2 3 | #!/usr/bin/env bash
docker stop slides && docker rm slides
|
We use the name slides
we specified when starting the container in
the previous section.
Start the cleanup with:
1 | $ ./clean.sh
|
My initial thought was to re-use this container for different presentations
where I only need to store the content in different files, all named
index.md
but in different directories. My local directory structure
looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | $ tree
.
|-- docker
| |-- build.sh
| |-- clean.sh
| |-- Dockerfile
| |-- index.html
| `-- run.sh
`-- slides
|-- topic-about-that
| |-- index.md
| `-- run.sh
`-- topic-about-this
|-- example.drawio.svg
|-- index.md
`-- run.sh
|
Depending on which presentation I want to show, I switch into that
directory and call ./run.sh
to let the bind mount of Docker
make the index.md
available inside the container.
The way I encapsulated Reveal.js here in a Docker container lets me use it without polluting my local laptop with things I rarely need.
The image I created is big (~700MB), which doesn’t cause any trouble because I have enough disk space, but I was still wondering if I could get that smaller with an alpine [2] or nodejs [3] base image instead of the ubuntu image.
To be honest, my initial goal was to have static HTML generated, which I can push to github pages or something similar. I couldn’t figure out if this was possible with Reveal.js.
The beautiful transitions between the slides only keep being beautiful if there is no lag, like in some screen sharing solutions. I’m going to use it in a face-to-face meeting in the next weeks (without any screen sharing), maybe it will work well for me in that scenario.