Draft Posts with Hakyll
Draft Posts are posts that don’t show up in your posts lists, but are available if you know the URL, so you can share them easily with a few selected editors before you officially publish them. They should not appear in the site’s archive or in any feed until published.
There are many different approaches to add support for draft posts to Hakyll.
Most of them involve a special metadata field, for example “published: true” or “draft: true”, to mark such posts as drafts.
I decided against a metadata field, in favor of a simpler solution: Draft posts are put into the posts/drafts
folder.
In order to publish them, you simply move them into the posts/
folder.
First we need a filter function that will filter out all draft posts from appearing in the archive or the feeds.
nonDrafts :: (MonadMetadata m, Functor m) => [Item a] -> m [Item a]
= return . filter f
nonDrafts where
= not . isPrefixOf "posts/drafts/" . show . itemIdentifier f
Then we create a new function that will replace the existing recentFirst
function.
recentFirstNonDrafts :: (MonadMetadata m, Functor m) => [Item a] -> m [Item a]
= do
recentFirstNonDrafts items <- nonDrafts items
nondrafts recentFirst nondrafts
Now s/recentFirst/recentFirstNonDrafts
in you hakyll configuration (usually site.hs
).
And since we introduced a sub folder in posts/
, the glob for posts need to be changed to look into sub folders too.
This is done by s;posts/*;posts/**;
Because draft posts don’t have a date set, we need to set a default date for every post without a date. First you need to configure a context that will add a default date for every draft post
defaultDateContext :: Context a
= Context $ \k i ->
defaultDateContext let itemPath = show $ itemIdentifier i in
if (isPrefixOf "posts/drafts/" itemPath) && (k == "date")
then (\_ -> do return (StringField "1970-01-01")) i
else empty
Then this context needs to configured as postCtx. I do this with
postCtx :: Tags -> Context String
= mconcat
postCtx tags
[ defaultContext
, defaultDateContext"date" "%B %e, %Y"
, dateField "tags" tags
, tagsField ]
Finally I’ve written a little convenience script to publish the posts.
It will check if the git repository is clean, move the draft posts into the posts/
folder and prepend a date string (i.e. the date the post got published).
#!/bin/bash -e
if ! [[ -f $1 ]]; then
echo "$1 is not a file"
exit 1
fi
if ! git diff --exit-code; then
echo "Error: Unstaged changes found, please stage your changes"
exit 1
fi
if ! git diff --cached --exit-code; then
echo "Error: Staged, but uncommited changes found, please commit"
exit 1
fi
declare -r POST=$(date +%Y-%m-%d)-$(basename ${1})
git mv $1 posts/${POST}
git commit -m "Published $POST"