Mike Slinn

jekyll_outline

Published 2020-10-03. Last modified 2024-07-21.
Time to read: 2 minutes.

This page is part of the jekyll_plugins collection.

This Jekyll tag plugin creates a clickable table of contents.

You can see it in action in several areas of this web site:

Examples

Django / Django-Oscar index.html

This is the simplest possible outline, without images.

/django/index.html
{% outline attribution django %}
0: Django / Oscar Evaluation
400: Notes
800: Digging Deeper
1900: Debugging
2700: Production
{% endoutline %}

A/V Studio index.html

This outline features images associated with specific entries.

/av_studio/index.html
{% outline attribution av_studio %}
0: General
150: Audio
200: Video
300: RME TotalMix
400: OBS Studio
500: Pro Tools
550: Ableton Live & Push
600: Other Music Software
700: MIDI Hardware & Software
800: Davinci Resolve
1000: Computer Analysis
2000: Music Theory
3000: Business
{% endoutline %}


<!-- #region images -->

<div style="display: none">
  <div style="display: none">
    {% img
      align="right"
      class=""
      id="outline_0"
      src="./images/handsfree/pageflip_firefly.webp"
      size="eighthsize"
      style="margin-top: 0"
      wrapper_class="clear"
    %}
    {% img
    align="right"
    class=""
    id="outline_150"
    src="./images/equipment/neumann/u87.webp"
    size="eighthsize"
    style="margin-top: 0"
    wrapper_class="clear"
  %}
  {% img
    align="right"
    class=""
    id="outline_200"
    src="./images/equipment/sony_a7iii/sony_a7iii.webp"
    size="eighthsize"
    style="margin-top: 0"
    wrapper_class="clear"
  %}
  {% img
    align="right"
    class=""
    id="outline_300"
    src="./images/rme/rme_logo.webp"
    size="eighthsize"
    style="margin-top: 0"
    wrapper_class="clear"
  %}
  {% img
    align="right"
    class=""
    id="outline_400"
    src="./images/obsStudio/obs_logo.webp"
    size="eighthsize"
    style="margin-top: 0"
    wrapper_class="clear"
  %}
  {% img
    align="right"
    class=""
    id="outline_500"
    src="./images/proTools/proToolsLogo.webp"
    size="eighthsize"
    style="margin-top: 0"
    wrapper_class="clear"
  %}
  {% img
    align="right"
    class=""
    id="outline_550"
    src="./images/ableton/ableton_live_logo.webp"
    size="eighthsize"
    style="margin-top: 0"
    wrapper_class="clear"
  %}
  {% img
    align="right"
    class="rounded"
    id="outline_600"
    src="./images/music21.webp"
    size="eighthsize"
    style="margin-top: 0"
    wrapper_class="clear"
  %}
  {% img
    align="right"
    class=""
    id="outline_700"
    src="./images/midi/MIDI_logo.webp"
    size="eighthsize"
    style="margin-top: 0"
    wrapper_class="clear"
  %}
  {% img
    align="right"
    class=""
    id="outline_800"
    src="./images/davinci_resolve/daVinci_resolve_logo.webp"
    size="eighthsize"
    style="margin-top: 0"
    wrapper_class="clear"
  %}
  {% img
    align="right"
    class="rounded"
    id="outline_2000"
    src="./images/music_theory.webp"
    size="eighthsize"
    style="margin-top: 0"
    wrapper_class="clear"
  %}
</div>
<!-- endregion -->

{% outline_js wrap_in_script_tag %}

Installation

Add the following line to your Jekyll project's Gemfile, within the jekyll_plugins group:

Shell
group :jekyll_plugins do 
  gem 'jekyll_outline'
end 

And then execute:

Shell
$ bundle

Fields

By default, each displayed entry consists of a document title, wrapped within an <a href></a> HTML tag that links to the page for that entry, followed by an indication of whether the document is visible (a draft) or not.

Entries can include following fields: draft, categories, description, date, last_modified_at, layout, order, title, slug, ext, tags, and excerpt.

The following example uses fields title and description:

Shell
{% outline fields="title – description" %}
000: Topic 0..19
020: Topic 20..39
040: Topic 40..
{% endoutline %}

Words in the fields argument that are not recognized as a field are transcribed into the output.

In the above example, notice that the HTML is space-delimited from the field names. The parser is simple and stupid: each token is matched against the known keywords. Tokens are separated by white space.

CSS

The CSS used for the demo website should be copied to your project.

demo/assets/css/jekyll_outline.css
.clearfix:after {
  content:"";
  display:block;
  clear:both;
 }

.outer_posts { }

.posts {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  line-height: 170%;
}

.posts > *:nth-child(odd) {
  font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace;
  font-stretch: semi-condensed;
  font-size: 10pt;
  width: 120px;
}

.posts > *:nth-child(even) {
  margin-bottom: 1em;
  width: calc(100% - 120px);
}

.post_title {
  margin-bottom: 0;
}

.outer_posts .jps_attribute {
  display: flex;
  margin-top: 1.5em;
}

.outline_error {
  background-color: rgb(248, 149, 149);
  border-radius: 4pt;
  border: rgb(155, 141, 141) 2px solid;
  color: black;
  font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace;
  font-stretch: semi-condensed;
  font-size: 9pt;
  line-height: 150%;
  padding: 2pt 4pt 2pt 4pt;
}

.outline_error code {
  color: lightgray;
  font-size: 10pt;
}

JavaScript

This project's outline_js tag returns the JavaScript necessary to position images relating to the outline. If used without parameters it just returns the JavaScript; use the tag this way:

Shell
<script> 
  {%= outline_js %}
</script> 

If passed the wrap_in_script_tag parameter, it wraps the JavaScript in <script></script>. Use the tag this way:

Shell
{% outline_js wrap_in_script_tag %}

Explanation

Given an outline that looks like this:

Shell
{% outline my_collection %}
000: Topic 0..19
020: Topic 20..39
040: Topic 40..
{% endoutline %}

...and given pages in the stuff collection with the following names:

  • 010-published.html has title Published Stuff Post 010
  • 020-unpublished.html has title Unpublished Post 020
  • 030-unpublished.html has title Unpublished Post 030

Then links to the pages in the stuff collection's pages are interleaved into the generated outline like this:

Shell
<div class="outer_posts">
  <h3 class='post_title clear' id="title_0">Topic 0..19</h3>
  <div id='posts_wrapper_0' class='clearfix'>
    <div id='posts_0' class='posts'>
      <span>2022-04-01</span> <span><a href='/stuff/010-published.html'>Published Stuff Post 010</a></span>
      <span>2022-04-17</span> <span><a href='/stuff/020-unpublished.html'>Unpublished Post 020</a> <i class='jekyll_draft'>Draft</i></span>
    </div>
  </div>
  <h3 class='post_title clear' id="title_20">Topic 20..39</h3>
  <div id='posts_wrapper_20' class='clearfix'>
    <div id='posts_20' class='posts'>
      <span>2022-04-17</span> <span><a href='/stuff/030-unpublished.html'>Unpublished Post 030</a> <i class='jekyll_draft'>Draft</i></span>
    </div>
  </div>
</div>

The JavaScript searches for images in the current page that were created by the jekyll_img plugin, and have ids that correspond to outline sections.

Each of following image’s ids have an outline_ prefix, followed by a number, which corresponds to one of the sections. Note that leading zeros in the first column above are not present in the ids below.

Headings that do not have corresponding pages are not displayed.

If you want to provide images to embed at appropriate locations within the outline, wrap them within an invisible div so the web page does not jump around as the images are loaded.

Shell
<div style="display: none;">
  {% img align="right"
    id="outline_0"
    size="quartersize"
    src="/assets/images/porcelain_washbasin.webp"
    style="margin-top: 0"
    wrapper_class="clear"
  %}
  {% img align="right"
    id="outline_20"
    size="quartersize"
    src="/assets/images/pipes.webp"
    style="margin-top: 0"
    wrapper_class="clear"
  %}
  {% img align="right"
    id="outline_40"
    size="quartersize"
    src="/assets/images/libgit2.webp"
    style="margin-top: 0"
    wrapper_class="clear"
  %}
</div>

The JavaScript identifies the images and repositions them in the DOM such that they follow the appropriate heading. If no image corresponds to a heading, no error or warning is generated. The images can be located anywhere on the page; they will be relocated appropriately. If an image does not correspond to a heading, it is deleted.

Attribution

See the jekyll_plugin_support plugin for an explanation of attribution.

Demo

A demo / test website is provided in the demo directory. It can be used to debug the plugin or to run freely. Please examine the HTML files in the demo to see how the plugin works.

  1. To run the demo freely from the command line, type:
    Shell
    $ demo/_bin/debug -r
  2. View the generated website at localhost:4444.
* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.