Related links in Github Pages

Jekyll comes with a built in ‘Related Links’ feature, however - if you run your Jekyll blog on Github Pages then it’s just the latest N posts.

Related posts by common tags would take about 3 lines of ruby, but because Github Pages is so locked down1 I must do it in Liquid. This really isn’t what liquid is for, but it doesn’t have to be fast or pretty - it’s only run once per deploy.

Here is my Liquid-only related posts script. It reminds me of the dark horror-filled days of Smarty templates. *shudder*

{% assign limit = '4' %}
{% assign matched = '' %}
{% for matching_tag_count in (1..page.tags.size) reversed %}
  {% for tag in page.tags %}
    {% for tag_page in site.tags[tag] %}
      {% if limit == '0' %}
        {% break %}
      {% else %}
      {% unless tag_page.url == page.url %}
      {% capture url_compare %}$${{tag_page.url}}$${% endcapture %}
      {% unless matched contains url_compare %}
      {% assign appearances = '0' %}
      {% for tag_page_tag in tag_page.tags %}
        {% if page.tags contains tag_page_tag %}
          {% capture appearances %}{{ appearances | plus:1 }}{% endcapture %}
        {% endif %}
      {% endfor %}
      {% capture difference %}{{ appearances | minus:matching_tag_count }}{% endcapture %}
      {% if difference == '0' %}
        {% capture matched %}{{matched}}{{url_compare}}{% endcapture %}
        {% capture limit %}{{limit | minus:1}}{% endcapture %}
        {% assign archive_page = tag_page %}
        {% include archive_row.html %}
      {% endif %}
      {% endunless %}
      {% endunless %}
      {% endif %}
    {% endfor %}
  {% endfor %}
{% endfor %}

I was going to go through and explain every line but liquid leaves me cold. I don’t understand it enough to know its limitations properly - such as why arithmetic works better with strings than numbers - I just do what it wants and be sad about the verbosity and inefficiency.


Flexy respondy tabs

Foundation 4 had a sections module that would be an accordion menu at narrow widths and a tab pane at wider widths.

This did horrible things with javascript - absolutely positioning the tabs using style attributes, after doing maths on them - making the tabs difficult to style differently than the supposed blank-slate foundation provides, which is probably why this is gone from later versions of foundation.

I heavily modified the module to copy the heading links into a separate div so I could use normal styling rather than javascript maths, but it was still very unsatisfying.

I have set out to recreate this using CSS alone - based mostly on the wonderful order flexbox property.

The rules:

  1. Don’t duplicate content.
  2. Use no unnecessary HTML elements
  3. Have a sensible/coherent source order
  4. No JavaScript!
  5. Flexible about the amount of content.
  6. Don’t worry about browser support - polyfills are a wonder.

The HTML I ended up with initially was very simple:

<div class="sections">
  <a href="#one" id="one" class="tab">One</a>
  <section><p>The first section</p></section>

  <a href="#two" id="two" class="tab">Two</a>
  <section><p>The second section</p></section>

  <a href="#three" id="three" class="tab">Three</a>
  <section><p>The third section</p></section>
</div>

The tabs target themselves so we can use the target pseudo-class .tab:target to select to the currently highlighted tab, and the sibling selector .tab:target + section to select its section.

But as I wrote this page and it got longer and became scrollabe the target was getting scrolled to the top. So, I’ll switch to using input[type="radio"] and :checked. woo.

<div class="sections">
  <input type="radio" id="one" checked>
  <label class="tab" for="one">One</label>
  <section><p>The first section</p></section>

  <input type="radio" id="two">
  <label class="tab" for="two">Two</label>
  <section><p>The second section</p></section>

  <input type="radio" id="three">
  <label class="tab" for="three">Three</label>
  <section><p>The third section</p></section>
</div>

Accordion styles

The css of the accordion menu is very straghtforward.

.sections .tab {
  position: relative;
  display: block;
}
.sections :checked + .tab {
  # highlighted styles
}

The sections are shown and hid using the animable max-height (as height:auto can’t be animated to.)

.sections section {
  transition: max-height 0.2s ease-out;
  max-height: 0;
  overflow: hidden;
}
.sections :checked + .tab + section {
  transition-delay: 0.15s;
  transition-duration: 0.5s;
  max-height: 100%;
  overflow: auto;
}

with a bit of extra visual style:

Tab styles

For the tabs we set the container to be display:flex, then we are able to pull all the tabs to the top of the container using order:1 for the tabs and order:2 for the content.

.sections {
  display: flex;
  flex-flow: wrap;
}
.sections .tab {
  order: 1;
  flex-grow: 1;
}
.sections :checked + .tab {
  # highlighted styles
}

We push the sections to the overlap using width:100%; margin-left:-100%;.

.sections section {
  order: 2;
  z-index: 1;
  position: relative;
  width :100%;
  margin-left: -100%;
  background: white;
}

The selected tab’s content gets stacked on top. Because of the wonder of flexbox all the tab panes are flexibly the same height, and so the first can be simply overlapped.

.sections :checked + .tab + section {
  z-index: 2;
}

And with all of this in place, we can slot everything into media queries and everything is wonderful, and easily add all the styles we could want in each case, without fighting with any js applied styles.


Hanging Punctuation

You may or may not have noticed that all my quote marks are outdented if they fall at the beginning of a line.

You may or may not think I’m crazy for not only caring, but writing a 50 line script to do it.

The typography nerds call this hanging punctuation.


The heart of any such script is, of course, a regular expression. I’m sorry.

The expression gets any series of quote marks (/[`'"“”‘’«»‹›]+/) that either begin at the start of a word (/^|\s/), or at the end of a word (/$|\s/). This successfully grabs all the quote marks and ignores all the apostrophes.

quote = /(?:(^|\s)([`'"“”‘’«»‹›]+)|([`'"“”‘’«»‹›]+)($|\s))/g

The next most important part of the process is a way to grab all the text nodes in a document. You’d think jQuery would just give you this, but it’s only 5 lines of coffeescript to roll our own.

This recursively steps through nodes, building up a jQuery object of just the text (nodeType == 3), until we have all the contiguous strings in the document and none of the HTML. Booya.

$.fn.textNodes = ->
  textNodes = $(this).contents().filter -> (this? && this.nodeType == 3)
  if $(this).children()[0]?
    $(this).children().textNodes().add(textNodes)
  else
    textNodes

Next we wrap the quotes in something we can grab and manipulate. We don’t want to affect anything inside a code element, or a table, and we want this script to be safe to run repeatedly without wrapping the already-wrapped.

$.fn.wrapQuotes = (className = 'hq', exclude = 'code,table') ->
  replacement = "$1<span class=\"#{className}\">$2$3</span>$4"
  exclude = "#{exclude},.#{className}"
  $(this).textNodes().each ->
    unless $(this).closest(exclude)[0]?
      $(this).replaceWith( $(this).text().replace(quote, replacement) )
  $(this)

Now that all our quote marks are surrounded by <span class="hq"></span> we can find out if they’re at the edge of their margin, and then we can outdent them.

Before that we’ll need yet another utility function, this to determine which edge/margin we want. This returns 'left', or 'right' based on the text alignment, and/or the language direction1.

$.fn.leadingEdge = ->
  align = $(this).css('textAlign')
  dir = $(this).css('direction')
  if align == 'left'  ||
     align == 'start' && dir == 'ltr' ||
     align == 'end'   && dir == 'rtl'
    'left'
  else
    'right'

Because we’re going to outdent these quote marks by absolutely positioning them, we need a way of applying a positioning context to the parent without overriding whatever styling they had. We set position: only if something’s not already set.

$.fn.relativise = ->
  $(this).each ->
    if $(this).css('position') == 'static'
      $(this).css(position:'relative')

With all those pieces in play, we can position the various quotes marks as necessary. First we find the elements we want and remove any styling they currently have. We get all the value we need using the aforementioned utility functions.

$.fn.hangQuotes = (className = 'hq') ->
  $(this).find(".#{className}").css(position:'',left:'',right:'').each ->

    parent = $(this).parent().relativise()
    edge = $(this).leadingEdge()
    parentPadding = parseFloat(parent.css("padding-#{edge}"), 10) # assume px
    textWidth = $(this).width()
    # ...

Then finally do the actual positioning. If the quote is against the edge, we first take it out of the document flow, and then check it’s still against the edge in case the reflow that we just triggered wrapped it to the previous line. We outdent it by its width.

    # ...
    if textWidth && $(this).position()[edge] == parentPadding
      $(this).css(position:'absolute')
      if $(this).position()[edge] == parentPadding
        outdent = textWidth - parentPadding
        $(this).css(edge, "#{-outdent}px")
      else
        $(this).css(position:'')

All & that remains is to assign & some events, so these scripts run when they need to. We would also need to run hangQuotes() after fonts have loaded, and wrapQuotes() after ajax has ajaxed.

$ ->
  $('body').wrapQuotes().hangQuotes()
$(window).resize ->
  $('body').hangQuotes()

And we’re done! Lovely typography at the expense of a small handful of javascripts and brains.

hang_quotes.js.coffee

  1. All the text in this blog is left-aligned, but that won’t necessarily always be the case, it’s a small robustness.


unscoped

unscoped is used by a number of pesky rails methods1.

This would be fine, except that occasionally, when doing sneaky things2 in Rails you want your default_scope to apply almost all the time.

Thus:

def self.unscoped(original = false)
  if original
    super()
  elsif block_given?
    super(&nil).default_scope.scoping { yield }
  else
    super().default_scope
  end
end

def self.really_unscoped
  unscoped(true)
end

That’s all for today.

(wow it’s like I’m writing an actual programming blog, with a post about some obscure hack that a total of 3 people will ever need).

  1. “Which methods?” you ask. update_columns and reload at least.

  2. “What kind of sneaky things?” you ask. Well, if you use non-unique ids and determine out which record with that id you want in the defualt_scope it becomes kind of necessary.3

  3. “Ok… Wait… What? Why would you do that?” you ask, quite reasonably. I run away.


Flexbox

Suppose I have a set of divs like so.

<div class="boxes">
  <div class="red box">1</div>
  <div class="green box">2</div>
  <div class="blue box">3</div>
</div>

If I want them to sit next to each other I could use float:left, though that can be annoying with the having to use clearfixes because it’s kind of a hack.

<style>
  .box { float: left; }
  .boxes::after {
    content: 'Hello I am a clearfix';
    clear: left;
    display: block;
  }
</style>

<div class="boxes">
  <div class="red box">1</div>
  <div class="blue box">2</div>
  <div class="green box">3</div>
</div>

Maybe it would be better if I used display:inline-block and pretend they’re inline elements (with a bit of font-size:0 to collapse whitespace)

<style>
  .boxes { font-size: 0; }
  .box {
    display: inline-block;
    font-size: 50px;
    vertical-align: bottom;
  }
</style>

<div class="boxes">
  <div class="red box">1</div>
  <div class="green box">2</div>
  <div class="blue box">3</div>
</div>

Or display:table; and display:table-cell; and pretend it’s an old-school table layout.

<style>
  .boxes { display: table; }
  .box { display: table-cell; }
</style>

<div class="boxes">
  <div class="red box">1</div>
  <div class="green box">2</div>
  <div class="blue box">3</div>
</div>

Move over

There is a new way. And the new way is flex-box. Behold!

All you need to do is give your wrapper element display:flex;.

<style>
  .boxes { display: flex; }
</style>

<div class="boxes">
  <div class="red box">1</div>
  <div class="green box">2</div>
  <div class="blue box">3</div>
</div>

Concepts

Flexbox is a new CSS display model (similar in scope to the block, inline, float, and table display models). It’s a part of the CSS spec, natively supported by all the latest versions of browsers.

Flex container

This is a outside block element that makes it all happen. It has display:flex or display:inline-flex. Everything happens because of this.

<style>
  .boxes {
    display: flex;
  }
</style>

<div class="boxes">
  Flex container
</div>

Flex item

All the immediate children of the flex container are flex items. (Child text nodes are automagically wrapped in an anonymous flex item.)

<style>
  .boxes {
    display: flex;
  }
</style>

<div class="boxes">
  <div class="red box">
    <div class="text">Flex item</div>
  </div>
  Text pretending to be a flex item.
</div>

Direction

Everything else in flexbox is defined relative to the flex direction, which is defined relative to the text direction of the current language (one of left-to-right, right-to-left, top-to-bottom, or bottom-to-top).

Reorder

Suppose I want to change the display order of elements, without affecting source order?

Set setting order:n; on each element will work.
The elements will be sorted by order value, with 0 as the default.
like z-index, order doesn’t require the numbers be sequential.

<style>
  .boxes { display: flex; }
  .red   { order: 99; }
  .green { order:  2; }
</style>

<div class="boxes">
  <div class="red box">1</div>
  <div class="blue box">2</div>
  <div class="green box">3</div>
</div>

look ma, no position:relative;

Direction

<style>
  .boxes {
    display: flex;
    flex-direction: column;
  }
</style>

<div class="boxes">
  <div class="red box">1</div>
  <div class="blue box">2</div>
  <div class="green box">3</div>
</div>

If you want the cells to be vertical then on the wrapping element you can set flex-direction:column; (the default value is row)

You can also set row-reverse or column-reverse which does what it says on the tin. These are relative to the text direction, so in an Arabic page the something with row will be look like row-reverse on an English page.

Alignment.

Perhaps we want our items centred. nightmarishly difficult, yes? no.

justify-content:center centres the elements along the flex-direction axis, while align-items:center works on the perpendicular axis:

align-items:center

<style>
  .boxes {
    height: 350px;
    display: flex;
    align-items: center;
  }
</style>

<div class="boxes">
  <div class="red box">1</div>
  <div class="green box">2</div>
  <div class="blue box">3</div>
</div>
<style>
  .boxes {
    height: 350px;
    display: flex;
    flex-direction: column;
    align-items: center;
  }
</style>

<div class="boxes">
  <div class="red box">1</div>
  <div class="green box">2</div>
  <div class="blue box">3</div>
</div>

justify-content:center;

<style>
  .boxes {
    height: 350px;
    display: flex;
    justify-content: center;
  }
</style>

<div class="boxes">
  <div class="red box">1</div>
  <div class="green box">2</div>
  <div class="blue box">3</div>
</div>
<style>
  .boxes {
    height: 350px;
    flex-direction: column;
    justify-content: center;
  }
</style>

<div class="boxes">
  <div class="red box">1</div>
  <div class="green box">2</div>
  <div class="blue box">3</div>
</div>

both!

<style>
  .boxes {
    height: 350px;
    display: flex;
    justify-content: center;
    align-items: center;
  }
</style>

<div class="boxes">
  <div class="red box">1</div>
  <div class="green box">2</div>
  <div class="blue box">3</div>
</div>
<style>
  .boxes {
    height: 350px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
  }
</style>

<div class="boxes">
  <div class="red box">1</div>
  <div class="green box">2</div>
  <div class="blue box">3</div>
</div>

Wow. That’s so easy. How much of my life have I wasted fighting CSS to center things, even in this very blog layout? never again (except all the times I need backwards compatibility)

The other options are flex-start and flex-end which align to the left or top, or right or bottom of the row or column, whichever makes the most sense based on the current flow direction.

justify-content: also has the option to space-around and space-between which spreads out the elements evenly. (space-between ignores the space on the ends). Wow. So many easy. So many simple.

If you have wrapped lines, then use align-content instead of align-items.

It’s also possible to set align-self: on an flex-item to give it a different alignment than all the other things, I can’t see why you’d want this, but maybe you’re a special snowflake.

Flex

Ladies and gentlement, this is the moment you’ve all been waiting for.

Flex is shorthand for flex-grow flex-shrink flex-basis so I’ll tackle them individually.

flex-basis:[length] (default is 0px). The length is used along the flex-direction axis. It’s basically the same as setting height or width.

if flex-grow is set to 0 then it won’t ever grow. It will be at most the flex-basis size. The equivalent for flex-shrink works also.

<style>
  .boxes { display: flex; }
  .box {
    flex-grow: 0;
    flex-shrink: 0;
  }
  .red   { flex-basis:  70px; }
  .green { flex-basis: 140px; }
  .blue  { flex-basis: 210px; }
</style>

<div class="boxes">
  <div class="red box">1</div>
  <div class="green box">2</div>
  <div class="blue box">3</div>
</div>

If the container is bigger than all the items lengths, then the flex-grow value is taken into account. This value represents its share of the remaining space.

In this example each flex item’s flex-grow value matches the number shown, so box 1 grows to fill 1/6 of the available space, box 2 grows to fill 1/3 of the available space, and box 3 grows to fill the final half of the available space.

<style>
  .boxes { display: flex; }
  .box { flex-basis: 70px; }
  .red   { flex-grow: 1; }
  .green { flex-grow: 2; }
  .blue  { flex-grow: 3; }
</style>

<div class="boxes">
  <div class="red box">1</div>
  <div class="green box">2</div>
  <div class="blue box">3</div>
</div>

If the container is smaller than all the items lengths, then the flex-shrink value is taken into account. This value is how much length is taken off, relative to other flex items flex-shrink value, to fit the row (relative to the length to be removed overall, not the length of the item. I think).

<style>
  .boxes { display: flex; }
  .box { flex-basis: 50%; }

  .red   { flex-shrink: 1; }
  .green { flex-shrink: 2; }
  .blue  { flex-shrink: 3; }
</style>

<div class="boxes">
  <div class="red box">1</div>
  <div class="green box">2</div>
  <div class="blue box">3</div>
</div>

I haven’t got a grasp on the default values for flex yet, so I’ve been overly specific - the default value of the short-hand flex are different than the defaults for completely unspecified flex values.

Wrap

The last thing we will learn today, before you go to an amazing page of ideas which you’ll now know how to use is: flex-flow

With this you can control the wrapping of the flex items.

<style>
  .boxes {
    height: 350px;
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
  }
</style>

<div class="boxes">
  <div class="red box">1</div>
  <div class="green box">2</div>
  <div class="blue box">3</div>
  <div class="purple box">4</div>
</div>
<style>
  .boxes {
    height: 350px;
    display: flex;
    align-content: flex-start;
    flex-wrap: wrap;
    flex-direction: column;

  }
</style>

<div class="boxes">
  <div class="red box">1</div>
  <div class="green box">2</div>
  <div class="blue box">3</div>
  <div class="purple box">4</div>
</div>

You can also wrap in the other direction with flex-wrap:wrap-reverse, and the other-other direction (with the help of flex-direction:*-reverse)

When wrapping is combined with the flex property, something magical happens.

<style>
  .boxes {
    height: 350px;
    display: flex;
    flex-wrap: wrap;
  }
  .box { flex-grow: 1; }
</style>

<div class="boxes">
  <div class="red box">1</div>
  <div class="green box">2</div>
  <div class="blue box">3</div>
  <div class="purple box">4</div>
</div>
<style>
  .boxes {
    height: 350px;
    display: flex;
    flex-wrap: wrap;
    flex-direction: column;
  }
  .box { flex-grow: 1; }
</style>

<div class="boxes">
  <div class="red box">1</div>
  <div class="green box">2</div>
  <div class="blue box">3</div>
  <div class="purple box">4</div>
</div>

That’s all we have time for, folks.

Look at Solved by flexbox for common design patterns that are simplified/solved by flexbox. Read MDN’s flexbox documentation for more details than I’ve given here.

You can play with flexbox yourself here.