3 min read

Add section links in blogdown

(Skip to my solution)

I find it extremely useful when blogs make it easy to link to sections within articles. I was looking for a way to do this with my own site and it turned out to be a bit harder than I expected. Nevertheless, I persisted and found a solution! While I found a solution in this thread, I needed to adapt it to make it work with blogdown.

The solution in the thread I went with is shown below:

Create layouts/partials/headline-hash.html with the following:

{{ . | replaceRE "(<h[2-9] id=\"([^\"]+)\".+)(</h[2-9]+>)" "${1}&nbsp;<a class=\"headline-hash\" href=\"#${2}\">#</a> ${3}" | safeHTML }}

Then use it where you insert page content as follow:

{{ partial "headline-hash.html" .Content }}

So, what is going on here? Well, basically this “partial” runs the content from a page through a filter that uses replaceRE to do a regular expression match and replacement. In this case, heading levels 2 through 9 are found. The regular expression has three groups defined by (...). The first matches the opening header tag <h..., the second matches the ID within the opening header tag [^\"]+, and the third captures the closing tag </h.... The replacement is then reconscructed from these groups, starting with the opening header tag (group 1 ${1}), followed by the anchor which is injected with the section ID (group 2 ${2}), and lastly the closing closing header tag (group 3 ${3}).

If you use blogdown this will only work if you use plain markdown, which gets processed with blackfriday. However, I write all my posts in rmarkdown, which gets processed with pandoc. Why does this matter? Well, the regular expression expects the secion header to be marked with an ID. Unfortunately, pandoc does not do this (for a good reason actually). Instead, pandoc assigns a section ID to a <div> that wraps the entire section. The resulting html will look something like this:

<div id="employment" class="section level1">
<h1>Employment</h1>
<!-- ... section content ... -->
</div>

Therefore, to get our regular expression working with pandoc generated html, we just need to match the <div>, not just the <h*>.

My solution 

  1. Create layouts/partials/anchored-headings.html with:

    {{ . | replaceRE "(<div id=\"([^\"]+)\".+class=\"section level[1-3].+\n<h[1-4]>.+)(</h[1-4]>)" "${1}&nbsp;<a class=\"section-anchor\" href=\"#${2}\"><i class=\"fas fa-link\"></i></a>${3}" | safeHTML }}
    <!-- yeah, it's a long line... -->

    I’m using the fontawesome link icon (<i class="fas fa-link"></i>), so replace this with whatever text you want for your link. To use fontawesome with blogdown, see my previous post.

  2. Edit the content part of layouts/_default/single.html from:

    {{ .Content }}

    To:

    {{ partial "anchored-headings.html" .Content }}
  3. To static/main.css, add the following:

    .section-anchor {
      font-size: 0.6em;
      vertical-align: super;
    }

    Of course, you can style this class however you want. I just went with a small, superscripted link icon.

If you found this post helpful, please say hi on twitter!