Dark Refraction

Jekyll Excerpt Plugin

When you run a blog, you often don't want the entire contents of each post to appear on the front page. You'd rather have just a small excerpt of each post show, and then have a link that says something like "Read More" which points to the actual post. While I was setting up this blog, I couldn't find any nice way to make jekyll do this, so I wrote a short plugin that provides this functionality.

Here's the code:

module JekyllPlugin

  class Excerpt < Liquid::Block
    def render(context)
      # Get the current post's post object
      id = context["page"]["id"]
      posts = context.registers[:site].posts
      post = posts [posts.index {|post| post.id == id}]

      # Put the block contents into the post's excerpt_tag field,
      # and also return those contents
      post.data["excerpt_tag"] = super
    end
  end

end

Liquid::Template.register_tag('excerpt', JekyllPlugin::Excerpt)

Using the plugin is simple. Just copy the code into a .rb file in your blog's _plugins directory. Then, surround the text you want to excerpt in {% excerpt %} and {% endexcerpt %} tags. After that, you can simply use post.excerpt_tag to get the contents of the excerpt tags. (Replace post with whatever your variable's name is while you're iterating through site.posts or paginator.posts)

As an example, on my index page I do something along these lines:

{% for post in site.posts %}
    {% if post.excerpt_tag %}
        {{ post.excerpt_tag | markdownify }}
    {% else %}
        {{ post.content }}
    {% endif %}
{% endfor %}

The if block is there so whenever I don't define an excerpt for a particular post, it simply shows the entire post.

Note that the markdownify part is one of jekyll's liquid extensions. The plugin doesn't capture the html of your post, it captures the text of the post before being converted by the markdown engine. Because of this, we need to filter post.excerpt_tag through the markdown converter before including the excerpt on the page. (Use the textilize filter instead if you want to use textile files instead of markdown files.)

One last usage note: If you access your post through the page variable in the liquid template, the excerpt_tag variable won't be available. In other words, page.excerpt_tag is always going to be undefined.

I suppose that the plugin's code deserves some explanation.

The first few lines of render() retrieve the post's object. You can access the page's variables easily by simply using context["page"], but if you try to modify them, the modifications will only be local to the current page. Instead, we want the changes to be global, just as if we had specified the variable in the YAML Front Matter. To do this, we get the site's global posts hash, and then simply iterate through the posts looking for one that matches the ID in context["page"]["id"]. The object we get is an instance of Jekyll::Post.

The last line of render() simply modifies post.data, which is where the variables from the YAML Front Matter live. After that, accessing post.excerpt_tag from the liquid template yields the desired value on all of the pages (including the index, which is where we need it).

Update (2013-06-14): Recent versions of jekyll started treating the variable excerpt specially, which caused problems. Renaming the variable to anything else will solve these issues. I therefore have renamed the variable to excerpt_tag above.

Update (2013-09-04): Jekyll 1.1 includes a class named Excerpt. Because I had previously (and foolishly) placed my class in the Jekyll module, this caused conflicts. This was fixed in the code above by moving the class to a module named JekyllPlugin.