jekyll-contentblocks

CI

Gives you a mechanism in Jekyll to pass content up from pages into their parent layouts. It's kind of like having Rails' content_for available for Jekyll.

Installation

jekyll-contentblocks supports Jekyll 3.0 and above. Jekyll 3.0–3.7 require Ruby 2.7 (they use APIs removed in Ruby 3), while Jekyll 3.8 and above run on Ruby 2.7 through 4.0. Jekyll versions below 3.0 are no longer supported.

The combinations exercised in CI (Ruby across the top, Jekyll down the side):

Jekyll \ Ruby 2.7.x 3.3.x 3.4.x 4.0.x
3.0.x
3.1.x
3.2.x
3.3.x
3.4.x
3.5.x
3.6.x
3.7.x
3.8.x
3.9.x
3.10.x
4.0.x
4.1.x
4.2.x
4.3.x
4.4.x

Add this line to your Jekyll project's Gemfile:

 group :jekyll_plugins do
   gem 'jekyll-contentblocks'
 end

Then execute:

$ bundle install

Standalone

Execute:

$ gem install jekyll-contentblocks

And initialize it in a plugin:

# _plugins/ext.rb
require "rubygems"
require "jekyll-contentblocks"

Usage

In your layout files, define contentblock blocks that say where content will end up. For example, say the file _layouts/default.html looks like this:

<html>
  <head>
    {% contentblock scripts %}
  </head>
  <body>
    <div class="main">
      {{ content }}
    </div>
    <div class="sidebar">
      {% contentblock sidebar %}
    </div>
  </body>
</html>

Now to add content to the sidebar from a post, you'd just need to do something like:

---
layout: default
---

Here is my post content.

{% contentfor sidebar %}
* Some content
* in a markdown list
* with some {{ 'liquid' }} tags too!
{% endcontentfor %}

Note that we didn't add anything to the scripts block in the post. That's OK, content blocks without any content will be ignored.

Skipping content conversion in a block

By default, a content block will be run through the converter for the current file (Markdown, for instance). Sometimes this is not desirable, such as for blocks containing code that shouldn't be modified. In the example above, content in the scripts block will be converted by default. To prevent this, add the no-convert option to the block, like this:

{% contentblock scripts no-convert %}

Now any content added to scripts will be placed in the block without any formatting applied.

Checking if a block has content

We might want to check whether a block has content before using it in our template. The simplest way is a plain Liquid if — each block name is exposed under contentblocks:

{% if contentblocks.sidebar %}
  {% contentblock sidebar %}
{% else %}
  <div>This is our default sidebar.</div>
{% endif %}

contentblocks.<name> is truthy whenever the block was defined on the page, and nil otherwise. Unlike the tags below, it also composes with other conditions:

{% if contentblocks.sidebar or page.force_sidebar %}
  ...
{% endif %}

The ifhascontent and ifnothascontent tags are also available:

{% ifhascontent javascripts %}
  <script type="text/javascript">
    {% contentblock javascripts %}
  </script>
{% endifhascontent %}

The difference is subtle: these tags test for non-empty content, whereas {% if contentblocks.name %} tests whether the block was defined at all. So a block that a page defines but leaves empty counts as "no content" for ifhascontent, yet is still present in contentblocks.

Repeating a block and reading its front matter

You can use the same block name more than once on a page. Each block is collected into an array available in the layout at contentblocks.<name>, so you can loop over the individual blocks instead of rendering them as one concatenated chunk.

Each block may also start with its own YAML front matter. The front matter is parsed into a data hash, and the remaining Markdown becomes content (converted just like a normal block). This lets an author write a few simple Markdown blocks while the layout arranges them into something richer.

For example, a page with a list of testimonials:

{% contentfor testimonial %}
---
author: Ada Lovelace
---
A **delightful** plugin.
{% endcontentfor %}

{% contentfor testimonial %}
---
author: Alan Turing
---
Saved us hours of work.
{% endcontentfor %}

The layout can then loop over them, reading each block's content and data:

<ul class="testimonials">
  {% for testimonial in contentblocks.testimonial %}
    <li>
      {{ testimonial.content }}
      <cite>{{ testimonial.data.author }}</cite>
    </li>
  {% endfor %}
</ul>

The original {% contentblock testimonial %} tag still works too — it renders all of the blocks together.

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

Running the tests

Try to make sure that your changes work with all of the latest point releases of Jekyll. To do this, run the test suite:

> bundle
> bundle exec appraisal install
> bundle exec appraisal rspec

Formatting

Ruby code is formatted with rubyfmt, which is installed (along with Ruby) via mise from the versions pinned in mise.toml:

> mise install                     # install Ruby + the pinned rubyfmt
> mise exec -- rubyfmt -i .         # format in place
> mise exec -- rubyfmt --check .    # check formatting (what CI runs)

CI runs rubyfmt --check as a gate before the test matrix, so unformatted code fails the build early.