Я изменил ваш site.hs
, чтобы создать элементарную страницу со списком тегов, которая, как мне кажется, имеет требуемую структуру: список тегов, каждый из которых содержит список сообщений с этим тегом.
Воткраткое описание каждой из вещей, которые мне пришлось сделать, чтобы заставить его работать:
{-# LANGUAGE ViewPatterns #-}
Не обязательно, но хорошее расширение языка, которое я использую один раз.Я думал, что буду использовать / упомянуть об этом, так как вы упомянули, что вы новичок в Haskell, и о нем приятно знать.
tags <- buildTags "posts/*" (fromCapture "tags/*.html")
В этой строке необходимо внести два изменения по сравнению с buildTags
в вашем начальном site.hs
.Один из них заключается в том, что он, вероятно, должен быть перемещен из отдельных предложений match
в монаду верхнего уровня Rules
, чтобы при необходимости мы могли создавать отдельные страницы тегов.Другое заключается в том, что захват был аналогичным образом изменен с "tags.html#"
на "tags/*.html"
.Это важно, потому что Hakyll хочет, чтобы у каждого Item
был уникальный Identifier
, и не все страницы тегов одинаковы.
Наличие отдельных страниц тегов с уникальными идентификаторами может не быть строго необходимым, но упрощаетостальная часть установки, так как большая часть машин Hakyll предполагает, что они существуют.В частности, строка Tags:
в отдельных описаниях постов ранее также не отображалась корректно.
По той же причине неплохо было бы на самом деле сделать эти отдельные страницы тегов маршрутизируемыми: без этого раздела вНа верхнем уровне Rules
монада, теги на каждом сообщении не будут правильно отображаться с используемым по умолчанию tagsField
, так как они не могут понять, как сделать ссылку на отдельную страницу тега:
tagsRules tags $ \tag pat -> do
route idRoute
compile $ do
posts <- recentFirst =<< loadAll pat
let postCtx = postCtxWithTags tags
postsField = listField "posts" postCtx (pure posts)
titleField = constField "title" ("Posts tagged \""++tag++"\"")
indexCtx = postsField <> titleField <> defaultContext
makeItem "" >>= applyTemplate postListTemplate indexCtx
>>= applyTemplate defaultTemplate defaultContext
>>= relativizeUrls
>>= cleanIndexUrls
Хорошо, это предварительные экзамены.Теперь перейдем к главной достопримечательности:
defaultCtxWithTags tags = listField "tags" tagsCtx getAllTags `mappend`
defaultContext
Хорошо, здесь добавлено важное поле tags
.Он будет содержать один элемент для каждой вещи, возвращаемой getAllTags
, а поля для каждого элемента будут заданы tagsCtx
.
where getAllTags :: Compiler [Item (String, [Identifier])]
getAllTags = pure . map mkItem $ tagsMap tags
where mkItem :: (String, [Identifier]) -> Item (String, [Identifier])
mkItem x@(t, _) = Item (tagsMakeId tags t) x
Что делает getAllTags
?Ну, это начинается с tagsMap tags
, как и ваш пример.Но Хэкилл хочет, чтобы результат был Item
, поэтому мы должны обернуть его, используя mkItem
.Что в Item
кроме тела?Просто Identifier
, и объект Tags
содержит поле, которое говорит нам, как это получить!Так что mkItem
просто использует tagsMakeId
для получения идентификатора и оборачивает данное тело этим идентификатором.
Как насчет tagsCtx?
tagsCtx :: Context (String, [Identifier])
tagsCtx = listFieldWith "posts" postsCtx getPosts `mappend`
metadataField `mappend`
urlField "url" `mappend`
pathField "path" `mappend`
titleField "title" `mappend`
missingField
Все, что начинается с metadataField
, этопросто обычные вещи, которые мы ожидаем получить от defaultContext
;мы не можем использовать defaultContext
здесь, так как он хочет добавить bodyField
, но тело этого Item
не является строкой (но вместо этого гораздо более полезная для нас структура Haskell, представляющая тег).Интересным моментом является строка, которая добавляет поле posts
, которое должно выглядеть немного знакомым.Большая разница в том, что он использует listFieldWith
вместо listField
, что в основном означает, что getPosts
получает дополнительный аргумент, который является телом Item
, в котором находится это поле.В данном случае это запись тега из tagsMap
.
where getPosts :: Item (String, [Identifier])
-> Compiler [Item String]
getPosts (itemBody -> (_, is)) = mapM load is
getPosts
, в основном просто использует функцию load
, чтобы получить Item
для каждого поста с учетом Identifier
-- это очень похоже на loadAll
, который вы делаете, чтобы получить все сообщения на странице индекса, но он просто дает вам одно сообщение.Странное на первый взгляд совпадение с шаблоном в действии ViewPatterns
в действии: в основном это говорит о том, что для соответствия этому шаблону шаблон справа от ->
(то есть (_, is)
) должен соответствовать результату примененияФункция слева (т.е. itemBody
) для аргумента.
postsCtx :: Context String
postsCtx = postCtxWithTags tags
postsCtx
очень проста: точно так же postCtxWithTags
используется везде, где мы визуализируем сообщение.
Это все, что нужно, чтобы получить Context
со всем, что вы хотите;осталось только создать шаблон для его визуализации!
tagListTemplateRaw :: Html
tagListTemplateRaw =
ul $ do
"$for(tags)$"
li ! A.class_ "" $ do
a ! href "$url$" $ "$title$"
ul $ do
"$for(posts)$"
li ! A.class_ "" $ do
a ! href "$url$" $ "$title$"
"$endfor$"
"$endfor$"
Это просто очень простой шаблон, который отображает вложенные списки;Вы, конечно, могли бы делать разные вещи, чтобы сделать его более привлекательным / привлекательным.
Я сделал пиар в вашем репо, чтобы вы могли увидеть эти изменения в контексте здесь .