summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/hakyll/examples/rssblog.zipbin6493 -> 6541 bytes
-rw-r--r--examples/hakyll/examples/tagblog.zipbin0 -> 6863 bytes
-rw-r--r--examples/hakyll/tutorial4.markdown214
-rw-r--r--examples/tagblog/hakyll.hs3
-rw-r--r--examples/tagblog/index.html3
5 files changed, 219 insertions, 1 deletions
diff --git a/examples/hakyll/examples/rssblog.zip b/examples/hakyll/examples/rssblog.zip
index c2d9ab1..0ff1046 100644
--- a/examples/hakyll/examples/rssblog.zip
+++ b/examples/hakyll/examples/rssblog.zip
Binary files differ
diff --git a/examples/hakyll/examples/tagblog.zip b/examples/hakyll/examples/tagblog.zip
new file mode 100644
index 0000000..645e982
--- /dev/null
+++ b/examples/hakyll/examples/tagblog.zip
Binary files differ
diff --git a/examples/hakyll/tutorial4.markdown b/examples/hakyll/tutorial4.markdown
new file mode 100644
index 0000000..e8db0fb
--- /dev/null
+++ b/examples/hakyll/tutorial4.markdown
@@ -0,0 +1,214 @@
+---
+title: Tutorial (Part IV)
+what: adds tags and context manipulations to our blog
+---
+
+Here, have [a zip file](examples/tagblog.zip) for this tutorial.
+
+## Context manipulations
+
+As you might remember, `Renderable` objects are usually just key-value mappings.
+We can render those with templates, and then the `$key`'s in the template get
+substituted by the appropriate values. This is a rather flexible system, but
+there are limitations. Some of these limitations can be solved using
+_Context Manipulations_.
+
+In `Text.Hakyll.Context`, we see the type of `ContextManipulation`:
+
+~~~~~{.haskell}
+type ContextManipulation = Context -> Context
+~~~~~
+
+Where `Context` is a key-value mapping. Usually, you don't create
+`ContextManipulation`'s by hand, but you use the `renderValue` function. Let's
+have a look at it's type.
+
+~~~~~{.haskell}
+renderValue :: String -> String
+ -> (ByteString -> ByteString)
+ -> ContextManipulation
+~~~~~
+
+This is the preferred way of creating context manipulations. The first argument
+is the `key` to manipulate. The second argument is the `key` where the new value
+should be placed. If this is the same as the first argument, it will be
+replaced. The third argument is the function to manipulate the `value` with.
+
+As a simple example, let's write a function that puts the `$title` in uppercase.
+
+~~~~~{.haskell}
+import qualified Data.ByteString.Lazy.Char8 as B
+import Data.Char (toUpper)
+
+titleUpper :: ContextManipulation
+titleUpper = renderValue "title" "title" $ B.map toUpper
+~~~~~
+
+## Applying Context Manipulations
+
+Now, the question is how to apply these `ContextManipulation`'s. The answer is
+simple. For every important render function (`render`, `renderChain`,
+`renderAndConcat`), there is a variant that takes a `ContextManipulation` as a
+first argument. These functions are thus: `renderWith`, `renderChainWith`,
+`renderAndConcatWith`. In fact, the following holds true:
+
+~~~~~{.haskell}
+render == renderWith id
+renderChain == renderChainWith id
+renderAndConcat == renderAndConcatWith id
+~~~~~
+
+So we could use or title manipulation like this:
+
+~~~~~{.haskell}
+renderChainWith titleUpper ["templates/default.html"]
+ (createPagePath "index.markdown")
+~~~~~
+
+## Rendering dates
+
+As you remember, in our previous blog, all posts had a file name like
+`posts/yyyy-mm-dd-title.extension`, as is the Hakyll convention. But they also
+had a metadata field `date`, containing a human-readable date. This is not very
+D.R.Y., of course! Hakyll has a specialized `renderValue` function to deal with
+dates encoded in paths: `renderDate`.
+
+~~~~~{.haskell}
+postManipulation :: ContextManipulation
+postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
+~~~~~
+
+That manipulation will:
+- Read the date from the file name the post was loaded from.
+- Parse the date and render it in a `%B %e, %Y` format. This is a
+ `Month day, Year` format.
+- Put the result in the `date` metadata field.
+- If the date could not be parsed, it will put `"Unknown date"` in the `date`
+ metadata field.
+
+So, we can throw away our `date: ` lines from our posts, and still use `$date`
+in our templates.
+
+## Abstracting the post list
+
+Now, we're going to render tags. This is also done using context manipulations.
+Hakyll has a specialized module to deal with tags, provided by
+`Text.Hakyll.Tags`. This module assumes tags are comma separated, and placed in
+the `tags` metadata field.
+
+~~~~~
+---
+title: A third post
+author: Publius Ovidius Naso
+tags: epic fail, ovidius
+---
+Pellentesque tempor blandit elit, vel...
+~~~~~
+
+But first things first. We need to render a post list for every tag. We already
+had some code to render a list of all posts. We're just going to abstract this
+code into a more general function:
+
+~~~~{.haskell}
+renderPostList url title posts = do
+ let postItems = renderAndConcatWith
+ postManipulation
+ "templates/postitem.html"
+ (map createPagePath posts)
+ customPage = createCustomPage
+ url
+ ("templates/postitem.html" : posts)
+ [ ("title", Left title)
+ , ("posts", Right postItems)
+ ]
+ renderChain ["posts.html", "templates/default.html"]
+ customPage
+~~~~~
+
+Our "render all posts" action can now be written as:
+
+~~~~~{.haskell}
+renderPostList "posts.html" "All posts" postPaths
+~~~~~
+
+## Tag links
+
+We want to display the tags for our post under the title. But if we use the
+`$tags` key in a template, we will just have the plain tags - they will not be
+clickable. We can again solve this with a `ContextManipulation`. We have a
+function that produces an url for a given tag:
+
+~~~~~{.haskell}
+tagToURL tag = "/tags/" ++ removeSpaces tag ++ ".html"
+~~~~~
+
+`removeSpaces` is an auxiliary function from `Text.Hakyll.File`. Now, there is
+a specialized `renderValue` function for creating linked tags called
+`renderTagLinks`. This function simply takes a function that produces an url
+for a given tag - the function we just wrote. Let's extend our
+`postManipulation`.
+
+~~~~~{.haskell}
+postManipulation :: ContextManipulation
+postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
+ . renderTagLinks tagToURL
+~~~~~
+
+If we click a tag, we get a `404`. That's because we haven't generated the
+post lists for every tag.
+
+## The Tag Map
+
+Hakyll provides a function called `readTagMap`. Let's inspect it's type.
+
+~~~~~{.haskell}
+readTagMap [FilePath] -> IO Map String [FilePath]
+~~~~~
+
+You give it a list of paths, and it creates a map that, for every tag, holds
+a number of posts. We can easily use this to render a post list for every tag.
+
+~~~~~{.haskell}
+tagMap <- readTagMap postPaths
+let renderListForTag (tag, posts) =
+ renderPostList (tagToURL tag)
+ ("Posts tagged " ++ tag)
+mapM_ renderListForTag (toList tagMap)
+~~~~~
+
+There we go. We now have clickable tags, and a post list for every tag.
+
+## A Tag Cloud
+
+A tag cloud is a commonly found thing on blogs. Hakyll also provides code to
+generate a tag cloud. Let's have a look at the `renderTagCloud` function.
+
+~~~~~{.haskell}
+TagCloud :: M.Map String [FilePath]
+ -> (String -> String)
+ -> Float
+ -> Float
+ -> String
+~~~~~
+
+The first argument is obviously the result of the `readTagMap` function. The
+second argument is, once again, a function to create an url for a given tag.
+Then, we give a minimum and a maximum font size in percent, and we get the
+tag cloud back. We can add this to our index:
+
+~~~~~{.haskell}
+let tagCloud = renderTagCloud tagMap tagToURL 100 200
+...
+createCustomPage "index.html"
+ ("templates/postitem.html" : take 3 postPaths)
+ [ ("title", Left "Home")
+ , ("posts", Right recentPosts)
+ , ("tagcloud", Left tagCloud)
+ ]
+~~~~~
+
+## That's it
+
+Feel free to hack around with the code from the zip file. As always, if you
+still have questions, ask them at the
+[google discussion group](http://groups.google.com/group/hakyll).
diff --git a/examples/tagblog/hakyll.hs b/examples/tagblog/hakyll.hs
index 330ba3b..1476360 100644
--- a/examples/tagblog/hakyll.hs
+++ b/examples/tagblog/hakyll.hs
@@ -35,7 +35,8 @@ main = hakyll $ do
(take 3 renderablePosts)
renderChain ["index.html", "templates/default.html"] $
createCustomPage "index.html" ("templates/postitem.html" : take 3 postPaths)
- [("title", Left "Home"), ("posts", Right recentPosts)]
+ [("title", Left "Home"), ("posts", Right recentPosts),
+ ("tagcloud", Left $ renderTagCloud tagMap tagToURL 100 200)]
-- Render all posts.
mapM_ (renderChainWith postManipulation
diff --git a/examples/tagblog/index.html b/examples/tagblog/index.html
index e628bd6..777433d 100644
--- a/examples/tagblog/index.html
+++ b/examples/tagblog/index.html
@@ -4,6 +4,9 @@
$posts
</ul>
<a href="/posts.html">All posts...</a>
+ <div>
+ $tagcloud
+ </div>
</div>
<div class="about">