Leaving bread crumbs and playing with Hakyll
One of the things I wanted most in my conversion to Hakyll was a way to show the location breadcrumbs of the current page. For those unfamiliar with the concept, these do not show the actual path the user took to get to the page, but they do show the location of the current page in the website hierarchy. For example, if I were on a post called "Having my bread crumbs and eating them too!", then the bread crumb URLs on the page would be as follows.
sast :: posts :: Having my bread crumbs and eating them too!
Having bread crumbs like these allows users to have a better feel for "where" they are in a website, and they are neat. Determining how best to create those bread crumbs using hakyll was not easy, and I am certain my implementation is overly complicated, but I finally have something that works! The key insight which led to a working implementation was the following:
An element of the page does not need to be visible.
That insight is not rocket science, and web designers are probably snickering
under their breath, but I had been struggling with how to get various levels of
the crumbs to appear and disappear depending on the visible level. The insight
started me down a path that ended with the idea of combining templates, inline
styles, and the CSS display
property.
First, I assume that the website has three different levels: main (index and facets), category (indexes of posts and projects), and page (individual posts and projects). Changing the levels would require changing the code, but with this pattern in place adding levels should (for certain values of should) be trivial.
Next, I include the following HTML in the default template (default.html
).
<div id="headercrumbs"> <a class="headercrumb" id="headerpagetitle" href="$url$">$title$</a> <span class="headercrumb" style="display:$categoryIs$">::</span> <a class="headercrumb" style="display:$categoryIs$" href="/$category$">$category$</a> :: <a class="headercrumb" id="sitelink" href="/">sast</a> </div>
Note that one of the crumb separators ("::") is bare, not surrounded by a
span
: this is because there will always be a "sitelink" (the link to the main
page of this website), and there will always be a "headerpagetitle". Thus,
there will always be at least one crumb separator and two links in the bread
crumbs.
Now comes the clever bit (or, at least I like to think it is the clever bit). Contexts are generated which contain two metadata fields. If the page is part of a category and not a category index, then the "category" field is set to the category name and the "categoryIs" field is set to "inline". If the page is not part of a category or is a category index, then the "category" field is set to the empty string and the "categoryIs" field is set to "none". All this magical manipulation happens with the help of three new contexts and two new functions, listed below.
defaultCtx :: Context String defaultCtx = mconcat [ crumbCtx , crumbIsCtx , defaultContext ] -- | Return String with the HTML code for bread crumbs of the current item. -- This assumes three levels to the site: main (index, facets), category -- (indices of posts, projects), page (individual posts, projects). -- crumbCtx :: Context a crumbCtx = field "category" $ \item -> do metadata <- getMetadata $ itemIdentifier item let crumb = findCategory (itemIdentifier item) categories return crumb crumbIsCtx :: Context a crumbIsCtx = field "categoryIs" $ \item -> do metadata <- getMetadata $ itemIdentifier item let crumbIs = findCategoryIs (itemIdentifier item) categories return crumbIs findCategory :: Identifier -> [String] -> String findCategory id cats | notElem '/' $ idPath = "" | hcat == [] = "" | hcat == head (splitDirectories $ idPath) = hcat | otherwise = findCategory id $ tail cats where idPath = toFilePath id hcat = head cats findCategoryIs :: Identifier -> [String] -> String findCategoryIs id cats | notElem '/' $ idPath = "none" | hcat == [] = "none" | hcat ++ "/index.md" == idPath = "none" | hcat == head (splitDirectories $ idPath) = "inline" | otherwise = findCategoryIs id $ tail cats where idPath = toFilePath id hcat = head cats
The defaultCtx
is only used when applying the default.html
template, and
thus should have minimal impact on other pages.
With the above code (and a bit of CSS) I have a happy little breadcrumb
navigation section on the upper-right corner of my web site which updates based
on the current page. It certainly is not the most elegant solution: the two
functions have a lot of duplication, as do the two contexts. For now though,
until I figure out how to use the output of findCategory
in findCategoryIs
,
it will do.