One of the benefits of using a static site generator is the infinite room for customization. The burden of using a static site generator is the endless room for customization. It’s a gift and a curse. Maintaining a blog is ultimately rewarding, but there are small hurdles we have to overcome.

Dabbling in Ruby can be thrilling and frustrating, packaged in a tornado of emotions, cursing, and victory. For Jekyll users, read on and learn from my mistakes and my ultimate success.

In this post, we’ll see how to extend Jekyll with some custom Liquid tags that allow us to share quotes and text snippets. The article will be a fun one because we’ll be using this post to use the same tag we build (so meta).

Jekyll Liquid Tags

Jekyll is a static site generator initially written and maintained by the folks at GitHub. While supported by GitHub, a team of volunteers continues the project today with contributions from the open-source community. Ruby powers Jekyll build-time engine, with the Liquid template engine doing most of the UI composition.

The design of Liquid lends to extensibility in the form of tags. There are two supported tag structures: inline and blocks.

The inline tag is a self-contained element with parameters found within the usage.

{% test "this is awesome" %}

Blocks, on the other hand, allow us to encapsulate content within a start and end tag.

{% test %}
    <p>woopsy doopsy</p>
{% endtest %}

We’ll be following the Jekyll tutorial to create one of each tag kind. In general, tags are created by adding a .rb file in the _plugins folder of our Jekyll site. For example, here is a RenderTimeTag that renders the generation time of the site.

module Jekyll
  class RenderTimeTag < Liquid::Tag

    def initialize(tag_name, text, tokens)
      super
      @text = text
    end

    def render(context)
      "#{@text} #{Time.now}"
    end
  end
end

Liquid::Template.register_tag('render_time', Jekyll::RenderTimeTag)

Twitter Share Tag and Tag Block

For our blog, we want to give ourselves the flexibility of sharing a single line of text, or a block of text. Let’s first look at the usage of a tag.

{% tweet "Khalid is pretty awesome at this Jekyll/Ruby stuff!" %}

The HTML result of our Liquid tag is pretty cool! You can click on the block quotes below.

The resulting tweet also appends the URL, the title of the post, and the relevant tags as hash-tags.

tag example

The tag block allows us to add breaks and display HTML elements.

{% tweetblock %}
"What can I say about Khalid?<br/>
He is pretty great, and you should follow him."<br/>
<cite>-- Everyone</cite>
{% endtweetblock %}

The resulting block renders with the HTML breaks and <cite> tag.

Sharing the block looks like this on Twitter.

tagblock example

We also get the advantage of our OpenGraph images populating the Twitter card.

The Implementation

We’ve gotten this far, let’s look at the implementation of our Twitter tags. Copy the following into _plugins/tweet.rb.

module Jekyll
    class TweetShareTag < Liquid::Tag
        # include all URL filters from Jekyll
        include Jekyll::Filters::URLFilters
        # include all standard liquid filters
        include Liquid::StandardFilters

        def initialize(tag_name, text, tokens)
            super
            @text = text.strip!
        end
        
        def render(context)
            # required for URLFilters
            @context = context
            
            # site/page context vars
            handle = context.environments.first["site"]["author"]["twitter"]
            title = context.environments.first["page"]["title"]
            hash_tags = context.environments.first["page"]["tags"].map { |t| "##{t}" }.join(" ")

            # Build vars for url
            url = url_encode(absolute_url(context.environments.first["page"]["url"]))
            tweet_text = url_encode(strip_html("#{@text} from #{title} #{hash_tags}"))

            "<blockquote>" +
            "   <a class=\"twitter-share\" href=\"http://twitter.com/share?text=#{tweet_text}&amp;url=#{url}&amp;via=#{handle}\">" +
            "       #{@text}<small><span class=\"fa-twitter fa\" aria-hidden=\"true\"></span> Tweet To Share</small>" +
            "   </a>" +
            "</blockquote>"
        end

        private

        def config
            @config ||= @context.registers[:site].config
        end
    end

    class TweetShareTagBlock < Liquid::Block
        # include all URL filters from Jekyll
        include Jekyll::Filters::URLFilters
        # include all standard liquid filters
        include Liquid::StandardFilters
        
        def render(context)
            # required for URLFilters
            @context = context
            text = super
            
            # site/page context vars
            handle = context.environments.first["site"]["author"]["twitter"]
            title = context.environments.first["page"]["title"]
            hash_tags = context.environments.first["page"]["tags"].map { |t| "##{t}" }.join(" ")

            # Build vars for url
            url = url_encode(absolute_url(context.environments.first["page"]["url"]))
            tweet_text = url_encode(strip_html("#{text} from #{title} #{hash_tags}"))

            "<blockquote>" +
            "   <a class=\"twitter-share\" href=\"http://twitter.com/share?text=#{tweet_text}&amp;url=#{url}&amp;via=#{handle}\">" +
            "       #{text} <small><span class=\"fa-twitter fa\" aria-hidden=\"true\"></span> Tweet To Share</small>" +
            "   </a>" +
            "</blockquote>"
        end

        private

        def config
            @config ||= @context.registers[:site].config
        end
    end
end

Liquid::Template.register_tag('tweet', Jekyll::TweetShareTag)
Liquid::Template.register_tag('tweetblock', Jekyll::TweetShareTagBlock)

Some cool things are happening in these tags. The first is we include the standard liquid filters and Jekyll filters. That means we get access to helper methods like absolute_url and strip_html. Secondly, we can access the entire site context, pulling in values for our own Twitter handle and title of the page. We can reduce the bloat of our tag by pulling in contextual information. Finally, we can strip_html and url_encode the href to make sure the blockquote renders correctly.

Shortcomings

The tags above do not account for the length of the tweet. If the tweet were to go over the limit, the user sharing would have to edit the tweet themselves manually. Secondly, both tags are similar and but do not share logic. We could have made a shared implementation, but felt it would not buy us much.

Another significant shortfall of tags is the ability to pass parameters. As you may notice with the TweetShareTag, we only get one text parameter. While it is a flexible construct, it is also annoying. Every tag implementation has to reinvent the parsing of the text input to parse variables and build time arguments. In this case, we don’t need any variables, but we could imagine passing additional values in the future. We’ll cross that bridge when we get there.

Finally, this HTML is specific to this blog, folks implementing this will need to modify the tags to produce blog relevant HTML.

Conclusion

While I spend most of my time working with .NET, I find hacking on this blog with Ruby just as enjoyable. If you are someone who does not like hacking in Ruby or wants something that works, this approach should get you most of the way there. Feel free to modify for your specific blog needs. Hoping this tag helps promote your content and pull in more readers.

If you have an idea to improve the tag, please leave a comment below.

P.S. Writing posts about Liquid tags is a pain in the ass, and you can quote me.