Next and previous links on blog postsFiled under: programming
Taking a break from presentations to work on this blog a bit. Finally added the links at the bottom of posts that link to the next and previous posts! Here’s how it looks like on the front end:
This design was originally created by Lucas Gatsas for his Jekyll theme. Beautiful and clean, definitely wanted to include it for this blog!
My first step to figure out how to do this was check the Haykll Tutorials and the Mailing List. The tutorials had no information about building pagination, but someone was so helpful about providing links in the mailing list. Thank goodness :) I picked the example provided by Richard Goulter, which generates a
prevPost value for each post. These two strings are generated on the Haskell side and plugged into the post template. The string values change per post.
I’m borrowing heavily from the original Jekyll template. Instead of ``, here we use Hakyll’s template tags which is defined in this example as
prevPost. We want to have the conditional
if(variable) statements for the edge casses of using this template for the very 1st post and very last post.
<ul class="pager"> if(prevPost) <li class="previous"> <a href="prevPost" data-toggle="tooltip" data-placement="top" title="Previous Post">← Previous Post</a> </li> endif if(nextPost) <li class="next"> <a href="nextPost" data-toggle="tooltip" data-placement="top" title="Next Post">Next Post →</a> </li> endif </ul>
The data attributes shown above for
data-placement are bootstrap specific, which defines the type of label that will show up when the mouse hovers over the link. The example shows a tooltip type that appears above the link.
Using Richard’s example, my Haskell code for creating the two strings looked like this:
match "posts/**.markdown" do route setExtension "html" compile do let postContext = field "nextPost" nextPostUrl `mappend` field "prevPost" previousPostUrl `mappend` defaultContext pandocCompiler >>= saveSnapshot "teaser" >>= loadAndApplyTemplate "templates/post.html" postContext >>= loadAndApplyTemplate "templates/default.html" postContext >>= relativizeUrls
The code says that for all the markdown files in subdirectory posts, create an html file with metadata defined for the default context (i.e. title) and include
nextPost gets its value from the function
nextPostUrl and similarly
prevPost gets its value from the function
previousPostUrl. The following functions are copied from his example.
previousPostUrl :: Item String -> Compiler String previousPostUrl post = do posts <- getMatches "posts/**.markdown" let ident = itemIdentifier post sortedPosts = sortIdentifiersByDate posts ident' = itemBefore sortedPosts ident case ident' of Just i -> (fmap (maybe empty toUrl) . getRoute) i Nothing -> empty nextPostUrl :: Item String -> Compiler String nextPostUrl post = do posts <- getMatches "posts/**.markdown" let ident = itemIdentifier post sortedPosts = sortIdentifiersByDate posts ident' = itemAfter sortedPosts ident case ident' of Just i -> (fmap (maybe empty toUrl) . getRoute) i Nothing -> empty
we also need some functions for sorting and finding the adjacent item and url of the post.
sortIdentifiersByDate :: [Identifier] -> [Identifier] sortIdentifiersByDate identifiers = reverse sortBy byDate identifiers where byDate id1 id2 = let fn1 = takeFileName toFilePath id1 fn2 = takeFileName toFilePath id2 myTime fn = parseTime defaultTimeLocale "%Y-%m-%d" intercalate "-" take 3 splitAll "-" fn in compare ((myTime fn1) :: Maybe UTCTime) ((myTime fn2) :: Maybe UTCTime) itemAfter :: Eq a => [a] -> a -> Maybe a itemAfter xs x = lookup x zip xs (tail xs) itemBefore :: Eq a => [a] -> a -> Maybe a itemBefore xs x = lookup x zip (tail xs) xs urlOfPost :: Item String -> Compiler String urlOfPost = fmap (maybe empty toUrl) . getRoute . itemIdentifier
Don’t forget your imports =)
import Data.Time.Format (parseTime) import System.Locale (defaultTimeLocale) import Data.Time.Clock (UTCTime) import System.FilePath (takeFileName) import Data.List (intercalate, sortBy) import Data.Maybe (fromMaybe) import Control.Applicative (Alternative (..))
That’s it! Thanks for reading this post. Enjoy the convenient links below!