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.
"Khalid is pretty awesome at this Jekyll/Ruby stuff!" Tweet To Share
The resulting tweet also appends the URL, the title of the post, and the relevant tags as hash-tags.
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.
"What can I say about Khalid?
He is pretty great, and you should follow him."
-- Everyone Tweet To Share
Sharing the block looks like this on Twitter.
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}&url=#{url}&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}&url=#{url}&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.
"Writing Jekyll posts about Liquid Tags is a Pain In The Ass!"--Khalid Tweet To Share