@@ -0,0 +1,214 @@
2+title: Tutorial (Part IV)
3+what: adds tags and context manipulations to our blog
6+Here, have [a zip file](examples/tagblog.zip) for this tutorial.
8+## Context manipulations
10+As you might remember, `Renderable` objects are usually just key-value mappings.
11+We can render those with templates, and then the `$key`'s in the template get
12+substituted by the appropriate values. This is a rather flexible system, but
13+there are limitations. Some of these limitations can be solved using
14+_Context Manipulations_.
16+In `Text.Hakyll.Context`, we see the type of `ContextManipulation`:
19+type ContextManipulation = Context -> Context
22+Where `Context` is a key-value mapping. Usually, you don't create
23+`ContextManipulation`'s by hand, but you use the `renderValue` function. Let's
24+have a look at it's type.
27+renderValue :: String -> String
28+ -> (ByteString -> ByteString)
29+ -> ContextManipulation
32+This is the preferred way of creating context manipulations. The first argument
33+is the `key` to manipulate. The second argument is the `key` where the new value
34+should be placed. If this is the same as the first argument, it will be
35+replaced. The third argument is the function to manipulate the `value` with.
37+As a simple example, let's write a function that puts the `$title` in uppercase.
40+import qualified Data.ByteString.Lazy.Char8 as B
41+import Data.Char (toUpper)
43+titleUpper :: ContextManipulation
44+titleUpper = renderValue "title" "title" $ B.map toUpper
47+## Applying Context Manipulations
49+Now, the question is how to apply these `ContextManipulation`'s. The answer is
50+simple. For every important render function (`render`, `renderChain`,
51+`renderAndConcat`), there is a variant that takes a `ContextManipulation` as a
52+first argument. These functions are thus: `renderWith`, `renderChainWith`,
53+`renderAndConcatWith`. In fact, the following holds true:
56+render == renderWith id
57+renderChain == renderChainWith id
58+renderAndConcat == renderAndConcatWith id
61+So we could use or title manipulation like this:
64+renderChainWith titleUpper ["templates/default.html"]
65+ (createPagePath "index.markdown")
70+As you remember, in our previous blog, all posts had a file name like
71+`posts/yyyy-mm-dd-title.extension`, as is the Hakyll convention. But they also
72+had a metadata field `date`, containing a human-readable date. This is not very
73+D.R.Y., of course! Hakyll has a specialized `renderValue` function to deal with
74+dates encoded in paths: `renderDate`.
77+postManipulation :: ContextManipulation
78+postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
81+That manipulation will:
82+- Read the date from the file name the post was loaded from.
83+- Parse the date and render it in a `%B %e, %Y` format. This is a
84+ `Month day, Year` format.
85+- Put the result in the `date` metadata field.
86+- If the date could not be parsed, it will put `"Unknown date"` in the `date`
89+So, we can throw away our `date: ` lines from our posts, and still use `$date`
92+## Abstracting the post list
94+Now, we're going to render tags. This is also done using context manipulations.
95+Hakyll has a specialized module to deal with tags, provided by
96+`Text.Hakyll.Tags`. This module assumes tags are comma separated, and placed in
97+the `tags` metadata field.
102+author: Publius Ovidius Naso
103+tags: epic fail, ovidius
105+Pellentesque tempor blandit elit, vel...
108+But first things first. We need to render a post list for every tag. We already
109+had some code to render a list of all posts. We're just going to abstract this
110+code into a more general function:
113+renderPostList url title posts = do
114+ let postItems = renderAndConcatWith
116+ "templates/postitem.html"
117+ (map createPagePath posts)
118+ customPage = createCustomPage
120+ ("templates/postitem.html" : posts)
121+ [ ("title", Left title)
122+ , ("posts", Right postItems)
124+ renderChain ["posts.html", "templates/default.html"]
128+Our "render all posts" action can now be written as:
131+renderPostList "posts.html" "All posts" postPaths
136+We want to display the tags for our post under the title. But if we use the
137+`$tags` key in a template, we will just have the plain tags - they will not be
138+clickable. We can again solve this with a `ContextManipulation`. We have a
139+function that produces an url for a given tag:
142+tagToURL tag = "/tags/" ++ removeSpaces tag ++ ".html"
145+`removeSpaces` is an auxiliary function from `Text.Hakyll.File`. Now, there is
146+a specialized `renderValue` function for creating linked tags called
147+`renderTagLinks`. This function simply takes a function that produces an url
148+for a given tag - the function we just wrote. Let's extend our
152+postManipulation :: ContextManipulation
153+postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
154+ . renderTagLinks tagToURL
157+If we click a tag, we get a `404`. That's because we haven't generated the
158+post lists for every tag.
162+Hakyll provides a function called `readTagMap`. Let's inspect it's type.
165+readTagMap [FilePath] -> IO Map String [FilePath]
168+You give it a list of paths, and it creates a map that, for every tag, holds
169+a number of posts. We can easily use this to render a post list for every tag.
172+tagMap <- readTagMap postPaths
173+let renderListForTag (tag, posts) =
174+ renderPostList (tagToURL tag)
175+ ("Posts tagged " ++ tag)
176+mapM_ renderListForTag (toList tagMap)
179+There we go. We now have clickable tags, and a post list for every tag.
183+A tag cloud is a commonly found thing on blogs. Hakyll also provides code to
184+generate a tag cloud. Let's have a look at the `renderTagCloud` function.
187+TagCloud :: M.Map String [FilePath]
188+ -> (String -> String)
194+The first argument is obviously the result of the `readTagMap` function. The
195+second argument is, once again, a function to create an url for a given tag.
196+Then, we give a minimum and a maximum font size in percent, and we get the
197+tag cloud back. We can add this to our index:
200+let tagCloud = renderTagCloud tagMap tagToURL 100 200
202+createCustomPage "index.html"
203+ ("templates/postitem.html" : take 3 postPaths)
204+ [ ("title", Left "Home")
205+ , ("posts", Right recentPosts)
206+ , ("tagcloud", Left tagCloud)
212+Feel free to hack around with the code from the zip file. As always, if you
213+still have questions, ask them at the
214+[google discussion group](http://groups.google.com/group/hakyll).