haskellpandochakyll

How to add Heading Links in Hakyll/Pandoc


I've got a Blog, generated using the Hakyll static site generator.

Hakyll uses Pandoc's markdown parser, which by default, generates IDs for each of the heading elements, based on their contents. These IDs can be used to link to individual sections, by appending an anchor with the ID to the page URL.

However, without inspecting the page source, it's difficult to tell what the ID for a given heading is. I'd like to add a self link to each heading, so visitors to the blog can use each heading as a link to that section in the post.


Solution

  • Here's how I achieved this. First off, some imports:

    import Text.Pandoc ( Pandoc, Inline(Link), Block(Header) )
    import Text.Pandoc.Walk (walk)
    import Text.Pandoc.Shared (stringify)
    

    We want a function that transforms Pandoc's Blocks.

    We want to match headers that have a level which is greater than 1 (because h1 elements only appear at the top of blog posts, so there's no point in linking to them).

    For these headers, we want to transform them, inserting a link. The link will have a destination generated from the ID of the header (with a # prepended to make it an anchor), and a title generated from the header contents.

    We also assign a class to the link, to help styling it, and set the contents of the link using the contents of the original header.

    transformHeader :: Block -> Block
    transformHeader header@(Header level attr@(identifier, _, _) contents) | level > 1 = 
        let linkClass = "headerLink"
            linkAttr = ("", [linkClass], [])
            linkDestination = "#" <> identifier
            linkTitle = stringify contents
        in Header level attr 
                [ Link linkAttr contents (linkDestination, linkTitle)
                ]
    transformHeader block = block 
    

    We can lift this into a function over Pandoc documents using walk.

    buildHeaderLinks :: Pandoc -> Pandoc
    buildHeaderLinks = walk transformHeader
    
    

    This can then be integrated into a Hakyll app using pandocCompilerWithTransform, or otherwise.

    I've tested this using pandoc-2.14.0.3, but it should be relatively flexible.