Middleman Tricks and Hacks
specific tricks I used to build this site
- tags
- middleman
- ruby
Contents
As part of the process of getting this site to work, I learned some more things about how to better build a site with middleman. Building off of our foundational article here are a few other things that I found very useful when using middleman to build a static site with a bunch of dynamically generated content.
Partials
The index.html.haml
, articles.html.haml
, tag.html.haml
and calendar.html.haml
pages all use the same partial to list out the post archives, which are mostly the same.
On the index page it’s called like this, where I’m supressing the date heading:
= partial "post_list", :locals => {:page_articles => blog.articles[1..4], :no_date => true }
and in the articles I’m including draft posts for my own reference, and since they don’t have a published date we need to check for that.
= partial "post_list", :locals => {:page_articles => (drafts + page_articles)}
The _post_list.haml
file then has some logic to show date headings based upon the published dates of the articles. (This assumes that the posts are sorted by time, either ascending or descending.)
- last_date = nil
- no_date = !!no_date
%ul
- page_articles.each do |current_post|
- if !no_date && current_post.date
- date_string = current_post.date.strftime('%b %Y')
- if last_date != date_string
%li.date
%h2= date_string
- last_date = date_string
%li
.more
- unless current_post.is_a? ::Middleman::Blog::Drafts::DraftArticle
= current_post.date.strftime( '%b %e' )
- else
Draft
%div= link_to current_post.title, current_post
%div
= current_post.data['subtitle']
.tags
- current_post.tags.sort.each do |tag|
.tag= link_to tag, tag_path( tag )
Partials also work better when using semantic CSS classes to define my layouts, since the same class can have different meaning depending upon what it is embedded in.
Layouts and partials for articles
Middleman posts are generally written in markdown, which translates into a series of <p>
tags that are dumped into a layout file. In order to create the table of contents on the left, the navigation to other articles on the right, the unique header and footer, I used a seperate article_layout
for article pages. Setting up Scrollspy and Affix means we need to change things on the <body>
tag that we don’t need to do for other pages, so it makes more sense to use a seperate file here rather than a nested layout.
This means that all the things that are shared between the two layouts, the main layout for all the meta pages and the article layout for the content pages, should be factored into partials. I put these partials in the layouts/
directory.
Communication between partials
The top and the bottom of these pages change together. If the page has a header image – something I specify in the YAML preamble of my post – then both the article_header
and footer
partials display slightly different things. The logic for this check is in the article_header
, where I set a instance variable that I use in a later partial to add a class.
In layouts/_article_header.haml
:
- @lighter ||= ""
- @dark_header = "dark_header" if current_article.data['dark_header']
- if !current_article.data['header_image'].nil? && current_article.data['header_image'] != ""
- @lighter = "lighter"
.banner
= image_tag current_article.data['header_image'], class: "fadeInDown animated"
%div{ class: "article-header #{@lighter} #{@dark_header}" }
and then in layouts/_footer.haml
I use the same variable to add a class to the footer
element which changes the background.
%footer{ class: "footer #{@lighter} #{@dark_header}" }
Markdown with toc data
Inside of config.rb
we can add some better markdown processing options. I switched to redcarpet and enabled with_toc_data
. This generates id tags on the <h1>
, <h2>
etc elements that we can use as anchors.
|
|
These ids are generated by sanitizing the text between the tags, but redcarpet
only makes things lowercase and changes spaces to underscores, and unfortunately it doesn’t strip out punctuation characters and will result in ids that aren’t valid. So I had to change my headers, at least until I can take a look at the redcarpet code in more detail.
Update This looks like it’s fixed in the latest git version of redcarpet, but it hasn’t been released as a gem yet.
Helpers that parse the source file
Now that we have the anchors in there, we need to generate the links to those anchors. This can be done by parsing the source file on the article page with a helper. It’s a poor man’s markdown processor, but it does the job. This code lives in config.rb
:
|
|
And we can then use it to generate the list of links:
%ul.nav.toc
%li= link_to current_article.title, "#top"
- chapters( current_article ).each do |chapter|
%li= link_to chapter, "##{chapter.downcase.gsub( /\s/, "-" )}"
Helper methods to do query-ish things
The logic to calculate the next and previous articles in the series work using the tag system, and it cycles though all of the tags of the current article to find articles with corresponding tags. Rather than showing the same article for multiple tags, I wanted to group the tags together if they all pointed to the same article.
This is the type of logic that would normally be in a rails Model. Either you’d do it directly out of the database, or you would process the results somehow and return something that was easy to iterate over in the view.
Moving this code into helper method isolated all of that logic out of the views themselves.
Site data as database
The other thing I wanted to do was to associate additional data with specific tags. If this was an article, you could put it in the preamble, but since tags are generated dynamically from the article files we need to put them somewhere else. That place is data/topics.yml
|
|
This is referenced in views like:
- data['topics'].each do |k,d|
.track
%h2= link_to d[:title], "/tags/#{k}.html"
This data is also referenced in the tag page as well as the main header. It’s only stored in one place, which is nice and DRY. If it got any more complicated than this, where we wanted to filter or sort it in some dynamic way then we implement that code in a helper so it could be shared across the site.
Directory index and url_for
To make pretty urls work in the blog, you need to have activate :directory_indexes
after activate :blog
in your config.rb
file. The order of middleman extensions in the config file matter.
The plugin works by changing the way that the link_to
helper works. If you have a link that’s generated in another way, you should use the url_for
method to make sure that it get’s rewritten. For example
|
|
Not a lot of tradeoffs
Other than the one issue with redcarpet where I couldn’t control the way that the ids were being generated, there hasn’t been much that I haven’t be able to achieve with a statically generated site. The implementation is different, but overall most of the time was spent fiddling with the CSS rather than fighting the build system.
Which is how it should be.
Previously
Next