@@ -1,209 +0,0 @@
1−---
2−title: TagBlog
3−what: adds tags and context manipulations to our blog
4−---
5−
6−Here, have [a zip file](examples/tagblog.zip) for this tutorial.
7−
8−## Context manipulations
9−
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_.
15−
16−In `Text.Hakyll.Context`, we see the type of `ContextManipulation`:
17−
18−~~~~~{.haskell}
19−type ContextManipulation = Context -> Context
20−~~~~~
21−
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.
25−
26−~~~~~{.haskell}
27−renderValue :: String -> String
28− -> (String -> String)
29− -> ContextManipulation
30−~~~~~
31−
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.
36−
37−As a simple example, let's write a function that puts the `$title` in uppercase.
38−
39−~~~~~{.haskell}
40−import Data.Char (toUpper)
41−
42−titleUpper :: ContextManipulation
43−titleUpper = renderValue "title" "title" $ map toUpper
44−~~~~~
45−
46−## Applying Context Manipulations
47−
48−Now, the question is how to apply these `ContextManipulation`'s. The answer is
49−simple. For every important render function (`render`, `renderChain`,
50−`renderAndConcat`), there is a variant that takes a `ContextManipulation` as a
51−first argument. These functions are thus: `renderWith`, `renderChainWith`,
52−`renderAndConcatWith`. In fact, the following holds true:
53−
54−~~~~~{.haskell}
55−render == renderWith id
56−renderChain == renderChainWith id
57−renderAndConcat == renderAndConcatWith id
58−~~~~~
59−
60−So we could use or title manipulation like this:
61−
62−~~~~~{.haskell}
63−renderChainWith titleUpper ["templates/default.html"]
64− (createPagePath "index.markdown")
65−~~~~~
66−
67−## Rendering dates
68−
69−As you remember, in our previous blog, all posts had a file name like
70−`posts/yyyy-mm-dd-title.extension`, as is the Hakyll convention. But they also
71−had a metadata field `date`, containing a human-readable date. This is not very
72−D.R.Y., of course! Hakyll has a specialized `renderValue` function to deal with
73−dates encoded in paths: `renderDate`.
74−
75−~~~~~{.haskell}
76−postManipulation :: ContextManipulation
77−postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
78−~~~~~
79−
80−That manipulation will:
81−- Read the date from the file name the post was loaded from.
82−- Parse the date and render it in a `%B %e, %Y` format. This is a
83− `Month day, Year` format.
84−- Put the result in the `date` metadata field.
85−- If the date could not be parsed, it will put `"Unknown date"` in the `date`
86− metadata field.
87−
88−So, we can throw away our `date: ` lines from our posts, and still use `$date`
89−in our templates.
90−
91−## Abstracting the post list
92−
93−Now, we're going to render tags. This is also done using context manipulations.
94−Hakyll has a specialized module to deal with tags, provided by
95−`Text.Hakyll.Tags`. This module assumes tags are comma separated, and placed in
96−the `tags` metadata field.
97−
98− ---
99− title: A third post
100− author: Publius Ovidius Naso
101− tags: epic fail, ovidius
102− ---
103− Pellentesque tempor blandit elit, vel...
104−
105−But first things first. We need to render a post list for every tag. We already
106−had some code to render a list of all posts. We're just going to abstract this
107−code into a more general function:
108−
109−~~~~{.haskell}
110−renderPostList url title posts = do
111− let list = createListingWith postManipulation
112− url "templates/postitem.html"
113− posts [("title", title)]
114− renderChain ["posts.html", "templates/default.html"] list
115−~~~~~
116−
117−As you can see, `createListing` also has a `xxxWith` variant that lets you
118−specify a `ContextManipulation` to apply on every `Renderable`. Our "render all
119−posts" action can now be written as:
120−
121−~~~~~{.haskell}
122−renderPostList "posts.html" "All posts" renderablePosts
123−~~~~~
124−
125−## Tag links
126−
127−We want to display the tags for our post under the title. But if we use the
128−`$tags` key in a template, we will just have the plain tags - they will not be
129−clickable. We can again solve this with a `ContextManipulation`. We have a
130−function that produces an url for a given tag:
131−
132−~~~~~{.haskell}
133−tagToUrl tag = "$root/tags/" ++ removeSpaces tag ++ ".html"
134−~~~~~
135−
136−`removeSpaces` is an auxiliary function from `Text.Hakyll.File`. Now, there is
137−a specialized `renderValue` function for creating linked tags called
138−`renderTagLinks`. This function simply takes a function that produces an url
139−for a given tag - the function we just wrote. Let's extend our
140−`postManipulation`.
141−
142−~~~~~{.haskell}
143−postManipulation :: ContextManipulation
144−postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
145− . renderTagLinks tagToUrl
146−~~~~~
147−
148−So, the `renderTagLinks` function replaces the `$tags` value from
149−`epic fail, random` to `<a href="$root/tags/epic-fail.html">epic fail</a>, ...`.
150−If we click a tag, we get a `404`. That's because we haven't generated the
151−post lists for every tag.
152−
153−## The Tag Map
154−
155−Hakyll provides a function called `readTagMap`. Let's inspect it's type.
156−
157−~~~~~{.haskell}
158−type TagMap = Map String [FilePath]
159−readTagMap String [FilePath] -> Hakyll TagMap
160−~~~~~
161−
162−You give it a list of paths, and it creates a map that, for every tag, holds
163−a number of posts. We can easily use this to render a post list for every tag.
164−The first argument given is an "identifier", unique to this tag map. Hakyll
165−needs this so it can cache the tags.
166−
167−~~~~~{.haskell}
168−tagMap <- readTagMap "postTags" postPaths
169−let renderListForTag (tag, posts) =
170− renderPostList (tagToUrl tag)
171− ("Posts tagged " ++ tag)
172−mapM_ renderListForTag (toList tagMap)
173−~~~~~
174−
175−There we go. We now have clickable tags, and a post list for every tag.
176−
177−## A Tag Cloud
178−
179−A tag cloud is a commonly found thing on blogs. Hakyll also provides code to
180−generate a tag cloud. Let's have a look at the `renderTagCloud` function.
181−
182−~~~~~{.haskell}
183−renderTagCloud :: TagMap
184− -> (String -> String)
185− -> Float
186− -> Float
187− -> String
188−~~~~~
189−
190−The first argument is obviously the result of the `readTagMap` function. The
191−second argument is, once again, a function to create an url for a given tag.
192−Then, we give a minimum and a maximum font size in percent, and we get the
193−tag cloud back. We can add this to our index:
194−
195−~~~~~{.haskell}
196−let tagCloud = renderTagCloud tagMap tagToUrl 100 200
197− index = createListingWith postManipulation "index.html"
198− "templates/postitem.html"
199− (take 3 renderablePosts)
200− [ ("title", "Home")
201− , ("tagcloud", tagCloud)
202− ]
203−~~~~~
204−
205−## That's it
206−
207−Feel free to hack around with the code from the zip file. As always, if you
208−still have questions, ask them at the
209−[google discussion group](http://groups.google.com/group/hakyll).