I wrote an abstraction for generating page navigation links in Hakyll that supports both web comic-style and blog post navigation.
On my site, I have both a Comics and a Posts section: the first is a collection of comic strips, and the section a collection of blog posts. I wanted them to behave slightly differently with respect to naviation: comics should have the common first/prev/next/last navigation links, whereas for posts I wanted to have less intrusive navigation links that only linked to the previous and next post.
For the comics, I came up with five navigation links, which are placed both above and below the strip, so that you can avoid scrolling down in order to zap through the strips. Additionally, a click on the strip itself takes you to the next comic.
Posts are read differently than comic strips: most users will come there from a feed aggregator (did I mention that this site provides an RSS feed linkin the top right corner?) or from results in a search engine, will then read an article and maybe start to browse around the site. I expect linear reading to be less common than for comic strips. Therefore I wanted more subdued navigation links. My favorite style for these links is to simply have a link to the previous and next posts, and to display the title of these posts.
The function below calculates both comic-style first/prev/next/last links and the titles of the previous and next page:
getNavigationContext :: [Identifier] -> (String -> String) -> Compiler (Context String) getNavigationContext pageIdents modifyLink = do ident <- getUnderlying let findIdent ident = find (\ (a, _) -> toFilePath a == toFilePath ident) let nextItem = fmap snd $ findIdent ident $ zip pageIdents (tail pageIdents) let prevItem = fmap snd $ findIdent ident $ zip (tail pageIdents) pageIdents let firstItem = case pageIdents of x:_ | toFilePath ident /= toFilePath x -> Just x _ -> Nothing let lastItem = case reverse pageIdents of x:_ | toFilePath ident /= toFilePath x -> Just x _ -> Nothing let maybeGetTitle = maybe (return Nothing) (flip getMetadataField "title") prevTitle <- maybeGetTitle prevItem nextTitle <- maybeGetTitle nextItem let maybeGetRoute = maybe (return Nothing) getRoute nextUrl <- maybeGetRoute nextItem prevUrl <- maybeGetRoute prevItem firstUrl <- maybeGetRoute firstItem lastUrl <- maybeGetRoute lastItem let maybeUrlField name = maybe mempty (constField name . toUrl . modifyLink) maybeConstField name = maybe mempty (constField name) let ctx = mconcat [ maybeUrlField "nextUrl" nextUrl, maybeUrlField "prevUrl" prevUrl, maybeUrlField "firstUrl" firstUrl, maybeUrlField "lastUrl" lastUrl, maybeConstField "nextTitle" nextTitle, maybeConstField "prevTitle" prevTitle ] return ctx
The function returns a context that contains the following fields:
Note that all fields that cannot be calculated (e.g. the prevUrl field on the first page) are not defined in the returned context.
One additional feature is that you can pass in a function to modify the file path of the first/prev/next/last links before they are converted to an URL. The example below shows how to make use of that. If you don’t need any modification, you can just pass in the id function.
I use the function in the following way:
pages <- getMatches (comicsPattern .&&. hasNoVersion) let htmlLink = flip replaceExtension "html" navCtx <- getNavigationContext pages htmlLink
The resulting navCtx is then used to render individual comic post pages. Note the use of htmlLink in the call to getNavigationContext in order to replace the .png or .jpg extensions my comic files have with .html.
For blog posts, the function is used like this:
posts <- getMatches postsPattern navCtx <- getNavigationContext posts id
No modification is done on the links. Because they are already correct.
Feel free to try out this little function in your Hakyll site – for me, it realizes exactly what I wanted!