@@ -1,221 +0,0 @@
1−---
2−title: Tags and manipulations
3−what: enhances our blog with tags and explains context manipulations.
4−---
5−
6−## Context manipulations
7−
8−Here, have [a zip file]($root/examples/tagblog.zip) for this tutorial.
9−
10−You probably remember that `Context` objects are just key-value mappings. We can
11−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−Like rendering actions, _context manipulations_ are also simply
17−`HakyllAction Context Context` arrows. The `Text.Hakyll.ContextManipulations`
18−contains some functions to easily construct easy variants.
19−
20−One of the most general functions is the `renderValue` function. Let's have a
21−look at it's type.
22−
23−~~~~~{.haskell}
24−renderValue :: String
25− -> String
26− -> (String -> String)
27− -> HakyllAction Context Context
28−~~~~~
29−
30−This is the preferred way of creating context manipulations. The first argument
31−is the `key` to manipulate. The second argument is the `key` where the new value
32−should be placed. If this is the same as the first argument, it will be
33−replaced. The third argument is the function to manipulate the `value` with.
34−
35−As a simple example, let's write a function that puts the `$title` in uppercase.
36−
37−~~~~~{.haskell}
38−import Data.Char (toUpper)
39−
40−titleUpper :: HakyllAction Context Context
41−titleUpper = renderValue "title" "title" $ map toUpper
42−~~~~~
43−
44−Because the destination `key` is the same as the source `key`, we can also use
45−the `changeValue` function here.
46−
47−~~~~~{.haskell}
48−titleUpper = changeValue "title" $ map toUpper
49−~~~~~
50−
51−For further reading, refer to the `Text.Hakyll.ContextManipulations`
52−documentation.
53−
54−## Applying Context Manipulations
55−
56−Because we're dealing with Arrows again, we can use `>>>` to apply our
57−manipulations. For example, we could use or title manipulation like this:
58−
59−~~~~~{.haskell}
60−renderChain ["templates/default.html"]
61− (createPage "index.markdown" >>> titleUpper)
62−~~~~~
63−
64−## Rendering dates
65−
66−As you remember, in our previous blog, all posts had a file name like
67−`posts/yyyy-mm-dd-title.extension`, as is the Hakyll convention. But they also
68−had a metadata field `date`, containing a human-readable date. This is not very
69−D.R.Y., of course! Hakyll has a specialized `renderValue` function to deal with
70−dates encoded in paths: `renderDate`.
71−
72−~~~~~{.haskell}
73−postManipulation :: HakyllAction Context Context
74−postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
75−~~~~~
76−
77−That manipulation will:
78−- Read the date from the file name the post was loaded from.
79−- Parse the date and render it in a `%B %e, %Y` format. This is a
80− `Month day, Year` format.
81−- Put the result in the `date` metadata field.
82−- If the date could not be parsed, it will put `"Unknown date"` in the `date`
83− metadata field.
84−
85−So, we can throw away our `date: ` lines from our posts, and still use `$date`
86−in our templates.
87−
88−## Abstracting the post list
89−
90−Now, we're going to render tags. This is also done using context manipulations.
91−Hakyll has a specialized module to deal with tags, provided by
92−`Text.Hakyll.Tags`. This module assumes tags are comma separated, and placed in
93−the `tags` metadata field.
94−
95− ---
96− title: A third post
97− author: Publius Ovidius Naso
98− tags: epic fail, ovidius
99− ---
100− Pellentesque tempor blandit elit, vel...
101−
102−But first things first. We need to render a post list for every tag. We already
103−had some code to render a list of all posts. We're just going to abstract this
104−code into a more general function:
105−
106−~~~~{.haskell}
107−renderPostList url title posts = do
108− let list = createListingWith url ["templates/postitem.html"]
109− posts [("title", Left title)]
110− renderChain ["posts.html", "templates/default.html"] list
111−~~~~~
112−
113−Our "render all posts" action can now be written as:
114−
115−~~~~~{.haskell}
116−renderPostList "posts.html" "All posts" renderablePosts
117−~~~~~
118−
119−## Tag links
120−
121−We want to display the tags for our post under the title. But if we use the
122−`$tags` key in a template, we will just have the plain tags - they will not be
123−clickable. We can again solve this with a `ContextManipulation`. We have a
124−function that produces an url for a given tag:
125−
126−~~~~~{.haskell}
127−tagToUrl tag = "$root/tags/" ++ removeSpaces tag ++ ".html"
128−~~~~~
129−
130−`removeSpaces` is an auxiliary function from `Text.Hakyll.File`. Now, there is
131−a specialized `renderValue` function for creating linked tags called
132−`renderTagLinks`. This function simply takes a function that produces an url
133−for a given tag - the function we just wrote. Let's extend our
134−`postManipulation`.
135−
136−~~~~~{.haskell}
137−postManipulation :: HakyllAction Context Context
138−postManipulation = renderDate "date" "%B %e, %Y" "Unknown date"
139− >>> renderTagLinks tagToUrl
140−~~~~~
141−
142−We apply this manipulation when we load the tags.
143−
144−~~~~~{.haskell}
145−let renderablePosts =
146− map ((>>> postManipulation) . createPage) postPaths
147−~~~~~
148−
149−So, the `renderTagLinks` function replaces the `$tags` value from
150−`epic fail, random` to `<a href="$root/tags/epic-fail.html">epic fail</a>, ...`.
151−If we click a tag, we get a `404`. That's because we haven't generated the
152−post lists for every tag.
153−
154−## The Tag Map
155−
156−Hakyll provides a function called `readTagMap`. Let's inspect it's type.
157−
158−~~~~~{.haskell}
159−type TagMap = Map String [HakyllAction () Context]
160−readTagMap String [FilePath] -> HakyllAction () TagMap
161−~~~~~
162−
163−You give it a list of paths, and it creates a map that, for every tag, holds
164−a number of posts. We can easily use this to render a post list for every tag.
165−The first argument given is an "identifier", unique to this tag map. Hakyll
166−needs this so it can cache the tags.
167−
168−~~~~~{.haskell}
169−let tagMap = readTagMap "postTags" postPaths
170−~~~~~
171−
172−When we have the `TagMap`, we can need to render a post list for every tag.
173−There is a function in Hakyll designed more or less for this purpose:
174−`withTagMap`. This takes a `TagMap` and an action to execute for every tag and
175−it's associated posts. We pass a small function to it we create ourselves[^1]:
176−
177−[^1]: Exercise for the reader: why do we use `>>> postManipulation` again here?
178−
179−~~~~~{.haskell}
180−let renderListForTag tag posts =
181− renderPostList (tagToUrl tag)
182− ("Posts tagged " ++ tag)
183− (map (>>> postManipulation) posts)
184−withTagMap tagMap renderPostList
185−~~~~~
186−
187−There we go. We now have clickable tags, and a post list for every tag.
188−
189−## A Tag Cloud
190−
191−A tag cloud is a commonly found thing on blogs. Hakyll also provides code to
192−generate a tag cloud. Let's have a look at the `renderTagCloud` function.
193−
194−~~~~~{.haskell}
195−renderTagCloud :: (String -> String)
196− -> Float
197− -> Float
198− -> HakyllAction TagMap String
199−~~~~~
200−
201−The first argument is, once again, a function to create an url for a given tag.
202−Then, we give a minimum and a maximum font size in percent, and we get a tag
203−cloud Arrow back. We can add this to our index:
204−
205−~~~~~{.haskell}
206−let tagCloud = tagMap >>> renderTagCloud tagToUrl 100 200
207− index = createListing "index.html"
208− ["templates/postitem.html"]
209− (take 3 renderablePosts)
210− [ ("title", Left "Home")
211− , ("tagcloud", Right tagCloud)
212− ]
213−renderChain ["index.html", "templates/default.html"] index
214−~~~~~
215−
216−## The gist of it
217−
218−- There's some handy, simple functions in `Text.Hakyll.ContextManipulations`.
219−- Seperate tags by commas and put them in the `$tags` field.
220−- Use `withTagMap` to render a list for every tag.
221−- Hakyll can also create tag clouds.