24 October 2023

Effortless Markdown Presentations in No Time

Have you ever found yourself in a pinch, needing to create a presentation but short on time? I definitely have been there, and it’s precisely where the simplicity of this method comes into play. With a few keystrokes and the right tools, you can turn your Markdown notes into a stylish presentation. No WYSIWYG editors required, no clicking through various visual objects. In this blog post, I’ll go over a method for creating visually appealing presentations in just 5 minutes.

It’s a journey from the comfort of your terminal to the applause at the end of your well-delivered presentation. I have to warn you though, you have to buckle up because initial setup might require some effort.

Technologies

This way of creating a presentation involves using several tools together.

Markdown - to create the presentation/write its source code. It is an easy markup language that is very much human-readable and easily stored as a plain text file.

Pandoc - to convert Markdown text into an actual presentation. It is a universal document converter. Just take a look at this terrifyingly big diagram. Looks like it can convert anything to anything else. But most importantly Markdown to reveal.js.

reveal.js - to render the presentation. It is a presentation framework built with web technologies.

You can use any text editor editor to author a Markdown file. Pandoc is the only thing you need to install. It’s a CLI tool so you’ll need a bit of terminal sweetness. In our case, pandoc produces an HTML file with presentation and reveal.js is already included so the only thing left to do is to open this HTML file in a browser.

In this blog post you wont see any of these technologies used to its max. If you want more, you can get more, just follow the links ^.~

Demo

Here’s what a final presentation may look like (click it to activate keyboard navigation):

Now let’s take a look at its source code:

index.md

% Easy Markdown presentation
% 10 october 2023 by ejiek

# Navigation

This presentation has vertival and horizonal navigation

## Vertical navigation

Uses second level of headings

# Horizontal navigation

Is based on first level of headings

# 

You don't have to have any title at all

# Partial reveal

One other way to show content

. . .

is delaying it

# Pictures
![](/android-chrome-512x512.png)

# Full screen pictures {data-background-image="/android-chrome-512x512.png"}

are just  backgrounds

# Full screen videos {data-background-video="/video_file.webm"}

# Tables

| Regular | Markdown |
|---------|----------|
| Tables  | Work     |

# Notes

Are only visible in the "Speaker view"

::: notes
And not rendered in the presentation
:::

The only thing that is missing right now is the video file.

🛠 Let’s build it

In a terminal, in the same directory with the demo file index.md:

pandoc -t revealjs --slide-level 2 -s -o index.html index.md
  • -t FORMAT is short for --to FORMAT which sets a destination format to convert to
  • --slide-level NUMBER enables vertical navigation for level 2 headings
  • -s is short for --standalone[=true|false] and it helps to produce a valid HTML with <head> and <body> instead of a document fragment
  • -o FILE specifies a file path to put the result of conversion to
  • index.md is a positional argument and specifies a document we want to convert

Now open index.html and enjoy your very own presentation 🎉

⌨️ Hot keys

Click the Demo above and try the following:

  • B fades the presentation to black. Useful when you need to talk
  • S opens the speaker view. An external window with the next slide preview, notes, and a timer
  • F full screen
  • Escape opens a navigation view
  • Alt+click to Zoom in (Ctrl+click for linux).

These keys should work out of the gate when you open a presentation but the Demo is embedded in another page, so it needs to be focused. You’re doing exactly that by clicking it - focusing the presentation.

🔁 Live preview

The command we’ve used before runs once, produces a result and stops. That cool when you know exactly what your presentation should look like from very beginning. Otherwise, it’s tedious.

Don’t you worry there is a way to update your presentation live just as you edit it. To achieve this I present you two new dependencies you have to install:

  • inotifywait to run pandoc on updates to the source file (when the change is saved)
  • live-server to update presentation in a browser when a new presentation file is generated

Here is a very simple script to run it all:

serve.sh

#!/usr/bin/env bash

build_presentation() {
  pandoc -t revealjs --slide-level 2 -s $FILE_TO_WATCH -o $OUTPUT_HTML
}

FILE_TO_WATCH="./index.md"
OUTPUT_HTML="index.html"

build_presentation
live-server --watch="${OUTPUT_HTML}" &
LIVE_SERVER_PID=$!

function stop_live_server {
  echo "Stopping live-server"
  kill -9 $LIVE_SERVER_PID
}

trap stop_live_server EXIT

while true; do
  inotifywait -e modify $FILE_TO_WATCH
  build_presentation
done

Don’t forget to make it executable and run ./serve.sh

trap is used to stop live-server when the script is stopped.

📦 Shipping it

After performing a great presentation it’s common to share the slides. There are two ways.

By default, HTML produced by pandoc doesn’t include local assets it links to. To make things a bit easier, there is an option to include all the assets into a single HTML file. Add --embed-resources to the pandoc build command:

pandoc -t revealjs --slide-level 2 -s --embed-resources -o index.html index.md

The second way is to export a presentation to a PDF file in a browser. reveal.js supports converting slides into printable pages. The result doesn’t usually look good, so I’d recommend sticking with the first option.

🛫 Offline builds

HTML file produced by pandoc uses an on-line version of reveal.js. Which can be a problem when you’re planning to go offline. It’s possible to override reveal.js location and even point it to a local directory. The only two things left to do it to get (git clone) reveal.js. Then point pandoc to it by adding -M revealjs-url="path/to/reveal.js" to the build command.

🔗 Showing off external sites

reveal.js allows to use <iframe> which make embedding sites as slides possible. That’s really neat! Unfortunately, not all sites like to be embedded this way into someone’s presentation. What if you’re going to say something bad about it …

Well, we’re going to say something anyway! Here’s an Atlassian page that talks dirty about gitflow being a legacy workflow - https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow.

Straight forward way to embed it would be:

## {data-background-iframe="https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow"}

It looks like this

and has the following error in the browser console Refused to frame 'https://www.atlassian.com/' because an ancestor violates the following Content Security Policy directive: "frame-ancestors 'none'".

Well, that’s quite unfortunate. We can tell our browser to ignore but it’s kind of magic that I don’t possess. We can use a reverse proxy to strip all the restrictions from the original page. One more dependency - Caddy. It supports 1-liners to do awesome stuff straight in a command line without a configuration file. I was not able to accomplish our goal in one line so here goes a Caddyfile:

{
    http_port 2080
}

localhost:2080 {
    route {
        header -Content-Security-Policy
        header -X-Frame-Options
        reverse_proxy https://www.atlassian.com {
            header_up Host {http.reverse_proxy.upstream.hostport}
        }
    }
}

Back to the presentation. Now we need to change https://www.atlassian.com to http://localhost:2080 and it should be a part of our presentation (with an annoying cookie overlay).

## {data-background-iframe="http://localhost:2080/git/tutorials/comparing-workflows/gitflow-workflow"}

❄️ Nix

For fellow Nix users, there is a small flake file to install dependencies flake.nix:

{
  description = "A minimal flake for presentation made with markdown, pandoc and reveal.js";

  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

  outputs = { self, nixpkgs, ... }: {

    devShell.x86_64-linux = with nixpkgs.legacyPackages.x86_64-linux; mkShell {
      buildInputs = [
        pandoc
        inotify-tools
        nodejs
        nodePackages.live-server
      ];

      shellHook = ''
        cat << EOF
        Update a FILE_TO_WATCH variable in serve.sh
        Run ./serve.sh
        Enjoy auto-updating presentation
        EOF
      '';
    };
  };
}

The following command gets you into the environment with all the dependencies:

nix develop

It should greet you with the following message:

Update a FILE_TO_WATCH variable in serve.sh
Run ./serve.sh
Enjoy auto-updating presentation

To start a live editing setup:

./serve

Even better - just one command:

nix develop -c ./serve.sh