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 nextPost
and 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.
HTML Markup
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-toggle
and 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.
Haskell
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
and prevPost
.
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!