Another reveal.js plugin for Jekyll

I use Jekyll to create my course websites and reveal.js to create my lecture slides. Both of them are awesome, and allow me to focus on writing (hopefully) great content, and the formatting/presentation stuff stays out of the way until I git push the updates to the server.

There are a few ways of making these two tools (Jekyll and reveal.js) play nicely together: see here and here for example. However, most of these require you to put each slide in a new .md file, which is a pain.

What I want to do is to write one .md file per presentation, and have the level 1 and level 2 headings (i.e. # and ##) determine the slide splits (this is pretty much how pandoc does it).

I wrote a simple Jekyll plugin to make this happen—which has just a couple of moving parts

Because the source for this whole blog is on GH, then you can just head there and see it for yourself if you’re the sort of person who prefers reading code to prose. Think of this blog post as a “companion piece”.

0. download the reveal.js source

It’s step 0 because it’s super easy—just head to GitHub, download & unzip the latest release. You can put it wherever you like in your main site folder; I usually put it in assets/.

1. the revealify filter

First, put this code into a revealify.rb file in your Jekyll _plugins directory:

# (c) Ben Swift 2017-2019, MIT Licence
# [email protected]

# a liquid filter for turning regular md output into the <section>-enclosed
# chunks required by reveal.js

require 'jekyll'
require 'nokogiri'

module Jekyll

  module Revealify

    def revealify(html)

      # parse content (wrapped in the reveal > slides divs)
      reveal_div = Nokogiri::HTML.fragment("<div class=\"reveal\"><div class=\"slides\">#{html}</div></div>", 'UTF-8')
      slides_div = reveal_div.search('.slides').first

      unless slides_div.first_element_child.matches? "section,h1,h2,hr"
        raise "reveal files must start with <section>, <h1>, <h2> or <hr>, not #{all_elements.first.name} (in \"#{@context.registers[:page]["path"]}\")"
      end

      slides_div.element_children.each do | element |

        # <section> elements should be passed through as-is
        if element.matches? "section"
          slides_div.add_child(element)

        else
          # on "split" elements (<h1>, <h2>, <hr>)
          if element.matches? "h1,h2,hr"
            current_section = slides_div.add_child("<section>").first
            # hoist all the header's attributes up to the wrapper element
            # not sure if this will always work, but here goes...
            element.keys.each do |attribute|
              # relies on the fact that the "current" wrapper node is the last child in ret
              current_section[attribute] = element[attribute]
              # element.delete attribute
            end
          end

          # add the element to the current <section> (i.e. the current slide)
          # unless it's just an <hr> (which are used for splitting only)
          if element.matches? "hr"
            element.unlink
          else
            slides_div.last_element_child.add_child(element)
          end
        end

      end

      reveal_div.to_html
    end

  end

end

Liquid::Template.register_filter(Jekyll::Revealify)

2. add a reveal layout

You’ll need a new layout as well: create a reveal.html file in your Jekyll _layouts directory and make sure that the body tag has this in it (you’ll need to make sure it’s got the right paths & other stuff for your setup). The key part is that first {{ content | revealify }} line—that takes the content of your page (the jekyll .md file with layout: reveal in the frontmatter) and passes it through the “revealify” filter plugin we made earlier.

The configuration stuff here is just the example config from reveal.js, so feel free to tweak to suit your own presentation.

<!-- this is where the reveailfy filter gets applied -->
{{ content | revealify }}

<!-- load the reveal.js css & js (assuming you've put it in assets/)-->
<link
  rel="stylesheet"
  href="{{site.baseurl}}/assets/reveal.js-3.8.0/css/reveal.css"
/>
<link
  rel="stylesheet"
  href="{{site.baseurl}}/assets/reveal.js-3.8.0/css/theme/white.css"
/>
<script
  src="{{site.baseurl}}/assets/reveal.js-3.8.0/js/reveal.js"
  type="text/javascript"
></script>

<!-- configure the presentation, (you can tweak options to suit) -->
<script>
  Reveal.initialize({
    // Display presentation control arrows
    controls: true,

    // Help the user learn the controls by providing hints, for example by
    // bouncing the down arrow when they first encounter a vertical slide
    controlsTutorial: true,

    // Determines where controls appear, "edges" or "bottom-right"
    controlsLayout: "bottom-right",

    // Visibility rule for backwards navigation arrows; "faded", "hidden"
    // or "visible"
    controlsBackArrows: "faded",

    // Display a presentation progress bar
    progress: true,

    // Display the page number of the current slide
    slideNumber: false,

    // Push each slide change to the browser history
    history: false,

    // Enable keyboard shortcuts for navigation
    keyboard: true,

    // Enable the slide overview mode
    overview: true
  });
</script>

The full layout file will depend on how the rest of your site works (where you’ve put the reveal.js-x.x.x folder, etc.) so I haven’t included the full file here (you can see it on GitHub, though). Also remember that you can see the full list of reveal configuration options in the README:

3. write your slides as markdown content

Finally, write your content as a regular jekyll post which uses the reveal layout, e.g.

---
title: "Week 1: intro"
layout: reveal
---

## Intro

- welcome to the course
- we're gonna learn all the things

## Timeline

- first, we'll sit in boring lectures...
- ... then, there will be a huge exam!

fun times.

Then, you get all the niceties of the jekyll watch cycle; livereload, auto-compilation of scss assets, etc.

And if you need to do something interesting with the formatting or layout of your content, then you can just drop straight into writing HTML (as you can always do in a markdown file).

4. write amazing content

This is the hard part. But at least if you’ve got a nice workflow for actually turning your content into nice looking slides then you’ve got a head start :)