Automatic Heading Anchors in Hakyll
If you only care about how I did it, you can click here to skip all the rambling!
Introduction
I like being able to deep link pages directly with their headings. Wikipedia, for example, provides this functionality by giving their headings the id
attribute. This allows constructing links with a #
like en.wikipedia.org/wiki/Third_place#Types
, which takes you directly to the corresponding heading.
My gripe with Wikipedia’s current solution is that they don’t provide a button near a heading to automatically fill in the URL bar with the deep link to that heading. You have to type the heading text (replacing spaces with underscores) by hand into the URL, which is super tedious if the heading is long. I want a clickable element per heading.
So, let’s do better than Wikipedia!
Of course, as the title suggests, my solution is intended to work for Hakyll (or anything that can be represented as a Pandoc AST for that matter!).
Existing Work
Cursory Google search did not lead me to an acceptable solution. I saw a solution where the anchor tags are inserted with JavaScript upon page load. I already have JS randomizing the greeting on the overview page of my blog, I don’t want any more JS. Besides, this is a Hakyll blog, so statically generated. Anchor tags are not dynamic elements, they are very much static, so this is a job for Hakyll, not for JS.
Solution
Hakyll provides an API for an integrated Pandoc Compiler. With pandocCompilerWithTransform
you can use this Pandoc Compiler with a custom transformation step (Pandoc -> Pandoc)
. I use walk
to traverse the Pandoc
AST and prepend my anchor element to each heading.
customPandocCompiler :: Compiler (Item String)
=
customPandocCompiler
pandocCompilerWithTransform
defaultHakyllReaderOptions
defaultHakyllWriterOptions$ walk prependAnchor
where
prependAnchor :: Block -> Block
Header lvl attr@(id', _, _) txts) =
prependAnchor (Header
lvl
attr
( toList
( linkWithmempty, ["anchor fas fa-xs fa-link"], mempty)
("#" <> id')
(mempty
mempty
)<> txts
)= x prependAnchor x
walk
has type (Block -> Block) -> Pandoc -> Pandoc
here, as Walkable Block Pandoc
is an instance of Walkable
.
prependAnchor
matches on a Header
, takes the id
that is generated for that heading by Pandoc and prepends <a href="#id" ... />
to the heading. ["anchor fas fa-xs fa-link"]
is simply a list of CSS classes so I can use FontAwesome for a nice anchor icon. Of course you can use anything as an anchor. Hint: use the pseudo-element ::before
in combination with the CSS rule content
to add anything without having to change the HTML structure.
If you want your anchor to be appended to the heading text, swap the arguments of the outer <>
.
The other arguments can be empty lists and empty strings, as they represent additional data like link text, link attributes, labels and so on. Just mempty
them all.
You can then use this custom Pandoc Compiler as you would for any Pandoc supported file type.
<=== Look at That Frog Anchor
Cool, it works! If I ever change the position of the frog and forget to adjust this heading, I will look like an idiot.