reciproka-web/site.hs
2024-10-29 23:29:51 +10:00

195 lines
7.5 KiB
Haskell

--------------------------------------------------------------------------------
{-# LANGUAGE OverloadedStrings #-}
import Control.Monad (forM_, liftM)
import Data.List (sortBy)
import Data.Monoid (mappend)
import Data.Ord (comparing)
import Hakyll
import System.FilePath (takeBaseName)
--------------------------------------------------------------------------------
main :: IO ()
main = hakyll $ do
match ("images/*" .||. "js/*") $ do
route idRoute
compile copyFileCompiler
match "css/*" $ do
route idRoute
compile compressCssCompiler
match "error/*" $ do
route $ (gsubRoute "error/" (const "") `composeRoutes` setExtension "html")
compile $ pandocCompiler
>>= applyAsTemplate siteCtx
>>= loadAndApplyTemplate "templates/default.html" (baseSidebarCtx <> siteCtx)
match "pages/*" $ do
route $ setExtension "html"
compile $ do
pageName <- takeBaseName . toFilePath <$> getUnderlying
let pageCtx = constField pageName "" `mappend`
baseNodeCtx
let evalCtx = functionField "get-meta" getMetadataKey `mappend`
functionField "eval" (evalCtxKey pageCtx)
let activeSidebarCtx = sidebarCtx (evalCtx <> pageCtx)
pandocCompiler
>>= saveSnapshot "page-content"
>>= loadAndApplyTemplate "templates/page.html" siteCtx
>>= loadAndApplyTemplate "templates/default.html" (activeSidebarCtx <> siteCtx)
>>= relativizeUrls
tags <- buildTags "posts/*" (fromCapture "tags/*.html")
match "posts/*" $ do
route $ setExtension "html"
compile $ do
posts <- loadAll ("posts/*" .&&. hasVersion "meta")
let taggedPostCtx = (tagsField "tags" tags) `mappend`
postCtx `mappend`
(relatedPostsCtx posts 3)
pandocCompiler
>>= saveSnapshot "content"
>>= loadAndApplyTemplate "templates/post.html" taggedPostCtx
>>= loadAndApplyTemplate "templates/default.html" (baseSidebarCtx <> siteCtx)
>>= relativizeUrls
create ["archive.html"] $ do
route idRoute
compile $ do
posts <- recentFirst =<< loadAllSnapshots ("posts/*" .&&. hasNoVersion) "content"
let archiveCtx =
listField "posts" postCtx (return posts) `mappend`
constField "title" "Archive" `mappend`
constField "archive" "" `mappend`
siteCtx
makeItem ""
>>= loadAndApplyTemplate "templates/archive.html" archiveCtx
>>= loadAndApplyTemplate "templates/default.html" (baseSidebarCtx <> archiveCtx)
>>= relativizeUrls
paginate <- buildPaginateWith postsGrouper "posts/*" postsPageId
paginateRules paginate $ \page pattern -> do
route idRoute
compile $ do
posts <- recentFirst =<< loadAllSnapshots (pattern .&&. hasNoVersion) "content"
let indexCtx =
constField "title" (if page == 1 then "Home"
else "Blog posts, page " ++ show page) `mappend`
listField "posts" postCtx (return posts) `mappend`
constField "home" "" `mappend`
paginateContext paginate page `mappend`
siteCtx
makeItem ""
>>= applyAsTemplate indexCtx
>>= loadAndApplyTemplate "templates/index.html" indexCtx
>>= loadAndApplyTemplate "templates/default.html" (baseSidebarCtx <> indexCtx)
>>= relativizeUrls
match "templates/*" $ compile templateBodyCompiler
create ["atom.xml"] $ do
route idRoute
compile $ do
let feedCtx = postCtx `mappend`
bodyField "description"
posts <- fmap (take 10) . recentFirst =<< loadAllSnapshots ("posts/*" .&&. hasNoVersion) "content"
renderAtom feedConfig feedCtx posts
--------------------------------------------------------------------------------
postsGrouper :: MonadFail m => MonadMetadata m => [Identifier] -> m [[Identifier]]
postsGrouper = liftM (paginateEvery 3) . sortRecentFirst
postsPageId :: PageNumber -> Identifier
postsPageId n = fromFilePath $ if (n == 1) then "index.html" else show n ++ "/index.html"
--------------------------------------------------------------------------------
feedConfig :: FeedConfiguration
feedConfig = FeedConfiguration
{ feedTitle = "Reciproka Kolektivo"
, feedDescription = "liberation, autonomy, privacy"
, feedAuthorName = "Reciproka Kolektivo"
, feedAuthorEmail = "kolektivo@reciproka.net"
, feedRoot = "https://reciproka.net"
}
--------------------------------------------------------------------------------
siteCtx :: Context String
siteCtx =
baseCtx `mappend`
constField "site_description" "Reciproka Kolektivo" `mappend`
constField "site-url" "https://reciproka.net" `mappend`
constField "tagline" "liberation, autonomy, privacy" `mappend`
constField "site-title" "Reciproka Kolektivo" `mappend`
constField "copy-year" "2024" `mappend`
constField "source-repo" "https://reciproka.dev/reciproka/reciproka-web" `mappend`
defaultContext
baseCtx =
constField "baseurl" "https://reciproka.net"
--------------------------------------------------------------------------------
postCtx :: Context String
postCtx =
dateField "date" "%B %e, %Y" `mappend`
defaultContext
tagsRulesVersioned tags rules =
forM_ (tagsMap tags) $ \(tag, identifiers) ->
rulesExtraDependencies [tagsDependency tags] $
create [tagsMakeId tags tag] $
rules tag identifiers
relatedPostsCtx
:: [Item String] -> Int -> Context String
relatedPostsCtx posts n = listFieldWith "related_posts" postCtx selectPosts
where
rateItem ts i = length . filter (`elem` ts) <$> (getTags $ itemIdentifier i)
selectPosts s = do
postTags <- getTags $ itemIdentifier s
let trimmedItems = filter (not . matchPath s) posts
take n . reverse <$> sortOnM (rateItem postTags) trimmedItems
matchPath :: Item String -> Item String -> Bool
matchPath x y = eqOn (toFilePath . itemIdentifier) x y
eqOn :: Eq b => (a -> b) -> a -> a -> Bool
eqOn f x y = f x == f y
sortOnM :: (Monad m, Ord b) => (a -> m b) -> [a] -> m [a]
sortOnM f xs = map fst . sortBy (comparing snd) . zip xs <$> mapM f xs
--------------------------------------------------------------------------------
sidebarCtx :: Context String -> Context String
sidebarCtx nodeCtx =
listField "list_pages" nodeCtx (loadAllSnapshots ("pages/*" .&&. hasNoVersion) "page-content") `mappend`
defaultContext
baseNodeCtx :: Context String
baseNodeCtx =
urlField "node-url" `mappend`
titleField "title" `mappend`
baseCtx
baseSidebarCtx = sidebarCtx baseNodeCtx
evalCtxKey :: Context String -> [String] -> Item String -> Compiler String
evalCtxKey context [key] item = (unContext context key [] item) >>= \cf ->
case cf of
StringField s -> return s
_ -> error $ "Internal error: StringField expected"
getMetadataKey :: [String] -> Item String -> Compiler String
getMetadataKey [key] item = getMetadataField' (itemIdentifier item) key