{-# LANGUAGE GHC2021 #-} {-# LANGUAGE OverloadedStrings #-} -- | Commonplace book: loads data/commonplace.yaml and renders -- themed and chronological HTML views for /commonplace. module Commonplace ( commonplaceCtx ) where import Data.Aeson (FromJSON (..), withObject, (.:), (.:?), (.!=)) import Data.List (nub, sortBy) import Data.Ord (comparing, Down (..)) import qualified Data.ByteString.Char8 as BS import qualified Data.Yaml as Y import Hakyll hiding (escapeHtml, renderTags) import Contexts (siteCtx) import Utils (escapeHtml) -- --------------------------------------------------------------------------- -- Entry type -- --------------------------------------------------------------------------- data CPEntry = CPEntry { cpText :: String , cpAttribution :: String , cpSource :: Maybe String , cpSourceUrl :: Maybe String , cpTags :: [String] , cpCommentary :: Maybe String , cpDateAdded :: String } instance FromJSON CPEntry where parseJSON = withObject "CPEntry" $ \o -> CPEntry <$> o .: "text" <*> o .: "attribution" <*> o .:? "source" <*> o .:? "source-url" <*> o .:? "tags" .!= [] <*> o .:? "commentary" <*> o .:? "date-added" .!= "" -- --------------------------------------------------------------------------- -- HTML rendering -- --------------------------------------------------------------------------- -- | Escape HTML, then replace newlines with
for multi-line verse. renderText :: String -> String renderText = concatMap tr . escapeHtml . stripTrailingNL where tr '\n' = "
\n" tr c = [c] stripTrailingNL = reverse . dropWhile (== '\n') . reverse renderAttribution :: CPEntry -> String renderAttribution e = "

\x2014\x202f" ++ escapeHtml (cpAttribution e) ++ maybe "" renderSource (cpSource e) ++ "

" where renderSource src = case cpSourceUrl e of Just url -> ", " ++ escapeHtml src ++ "" Nothing -> ", " ++ escapeHtml src renderTags :: [String] -> String renderTags [] = "" renderTags ts = "
" ++ concatMap (\t -> "" ++ escapeHtml t ++ "") ts ++ "
" renderEntry :: CPEntry -> String renderEntry e = concat [ "
" , "

" , renderText (cpText e) , "

" , renderAttribution e , maybe "" renderCommentary (cpCommentary e) , renderTags (cpTags e) , "
" ] where renderCommentary c = "

" ++ escapeHtml c ++ "

" -- --------------------------------------------------------------------------- -- Themed view -- --------------------------------------------------------------------------- -- | All distinct tags in first-occurrence order (preserves YAML ordering). allTags :: [CPEntry] -> [String] allTags = nub . concatMap cpTags renderTagSection :: String -> [CPEntry] -> String renderTagSection tag entries = concat [ "
" , "

" ++ escapeHtml tag ++ "

" , concatMap renderEntry entries , "
" ] renderThemedView :: [CPEntry] -> String renderThemedView [] = "
" ++ "

No entries yet.

" ++ "
" renderThemedView entries = "
" ++ concatMap renderSection (allTags entries) ++ (if null untagged then "" else renderTagSection "miscellany" untagged) ++ "
" where renderSection t = let es = filter (elem t . cpTags) entries in if null es then "" else renderTagSection t es untagged = filter (null . cpTags) entries -- --------------------------------------------------------------------------- -- Chronological view -- --------------------------------------------------------------------------- renderChronoView :: [CPEntry] -> String renderChronoView entries = "" where sorted = sortBy (comparing (Down . cpDateAdded)) entries -- --------------------------------------------------------------------------- -- Load entries from data/commonplace.yaml -- --------------------------------------------------------------------------- loadCommonplace :: Compiler [CPEntry] loadCommonplace = do rawItem <- load (fromFilePath "data/commonplace.yaml") :: Compiler (Item String) let raw = itemBody rawItem case Y.decodeEither' (BS.pack raw) of Left err -> fail ("commonplace.yaml: " ++ show err) Right entries -> return entries -- --------------------------------------------------------------------------- -- Context -- --------------------------------------------------------------------------- commonplaceCtx :: Context String commonplaceCtx = constField "commonplace" "true" <> themedField <> chronoField <> siteCtx where themedField = field "cp-themed-html" $ \_ -> renderThemedView <$> loadCommonplace chronoField = field "cp-chrono-html" $ \_ -> renderChronoView <$> loadCommonplace