1{-# LANGUAGE OverloadedStrings #-}2{-# LANGUAGE ViewPatterns #-}34import Data.Maybe (fromJust)5import qualified Data.Char as Char6import qualified Data.Text as T7import Hakyll hiding (renderTagList)8import Hakyll.Core.Compiler.Internal (compilerTellDependencies)9import Hakyll.Core.Dependencies10import System.FilePath (replaceExtension, takeExtension)11import Text.Blaze ((!))12import Text.Blaze.Html.Renderer.String (renderHtml)13import qualified Text.Blaze.Html5 as H14import qualified Text.Blaze.Html5.Attributes as A15import qualified Text.Pandoc as P16import Text.Pandoc.Highlighting (Style, haddock, styleToCss)17import Text.Pandoc.Walk (walk)1819toLower :: String -> String20toLower = fmap Char.toLower2122------------------------------------------------------------------------2324linkToTag :: T.Text -> P.Inline25linkToTag name =26 let desc = "All pages tagged '" `T.append` name `T.append` "'"27 file = "/tags/" `T.append` T.toLower name `T.append` ".html"28 in P.Link ("", [], []) [P.Str name] (file, desc)2930-- Transform zk references to .md files to .html files.31--32-- TODO: Filter out references to private notes.33-- TODO: Support note references with anchors (e.g., for referencing a section).34fixupNoteRefs :: Item String -> Compiler (Item String)35fixupNoteRefs = pure . fmap (withUrls go)36 where37 go :: String -> String38 go url39 | isZkRef url = replaceExtension url ".html"40 | otherwise = url4142 -- Returns true if the URL is a reference to another zk note.43 isZkRef :: String -> Bool44 isZkRef ('#' : _) = False45 isZkRef url =46 let ext = takeExtension url47 in not (isExternal url) && (ext == "" || ext == ".md")4849-- Implements support for tags using Bear's multi-word tag syntax.50--51-- See:52-- • https://github.com/zk-org/zk/blob/dev/docs/notes/tags.md>53-- • https://github.com/zk-org/zk/blob/v0.15.1/internal/adapter/markdown/extensions/tag.go#L7954inlineBearTags :: [P.Inline] -> [P.Inline]55inlineBearTags (i@(P.Str (T.stripPrefix "#" -> Just tagRst)) : ix) =56 case takeTagElems (P.Str tagRst : ix) of57 Nothing -> i : inlineBearTags ix58 Just el ->59 let (tag, rst) = splitTag $ T.unwords el60 numElement = (length el - 1) * 2 -- count P.Space too61 in [linkToTag tag, P.Str rst] ++ inlineBearTags (drop numElement ix)62 where63 takeTagElems :: [P.Inline] -> Maybe [T.Text]64 takeTagElems (P.Str str : xs)65 | T.elem '#' str = Just [str]66 | otherwise = (str :) <$> takeTagElems xs67 takeTagElems (P.Space : xs) = takeTagElems xs68 takeTagElems _ = Nothing6970 splitTag :: T.Text -> (T.Text, T.Text)71 splitTag t = splitAtEx (fromJust $ T.findIndex (== '#') t) t7273 -- Like T.splitAt because exclude the seperator in 'snd'.74 splitAtEx :: Int -> T.Text -> (T.Text, T.Text)75 splitAtEx n t = let (b, a) = T.splitAt n t in (b, T.drop 1 a)76inlineBearTags (i : ix) = i : inlineBearTags ix77inlineBearTags [] = []7879pandocCodeStyle :: Style80pandocCodeStyle = haddock8182pandocCompilerZk :: Compiler (Item String)83pandocCompilerZk =84 cached "pandocCompilerZk" $85 (pandocCompilerZk' >>= fixupNoteRefs)86 where87 pandocCompilerZk' :: Compiler (Item String)88 pandocCompilerZk' =89 pandocCompilerWithTransform90 defaultHakyllReaderOptions91 { P.readerStripComments = True }92 defaultHakyllWriterOptions93 { P.writerHighlightStyle = Just pandocCodeStyle }94 (walk transform)9596 transform :: P.Block -> P.Block97 transform = walk inlineBearTags9899------------------------------------------------------------------------100101getMetadataItems :: Pattern -> Compiler [Item Metadata]102getMetadataItems pattern = map (uncurry Item) <$> getAllMetadata pattern103104-- Custom version of Hakyll's renderTagList.105-- TODO: Maybe produce a Context here.106renderTagList :: Tags -> Compiler String107renderTagList = renderTags makeLink concat108 where109 makeLink tag url count _minCount _maxCount =110 renderHtml . H.li111 $ H.a112 ! A.href (H.toValue url)113 ! A.class_ "tag"114 ! A.title ("Navigate posts by tag '" <> H.stringValue tag <> "'")115 $ H.toHtml (tag ++ " (" ++ show count ++ ")")116117------------------------------------------------------------------------118119config :: Configuration120config = defaultConfiguration121 { deployCommand = "./deploy.sh" }122123main :: IO ()124main = hakyllWith config $ do125 match "css/*" $ do126 route idRoute127 compile compressCssCompiler128 create ["css/syntax.css"] $ do129 route idRoute130 compile $131 makeItem $ (compressCss $ styleToCss pandocCodeStyle)132133 tags <- buildTags "notes/*" (fromCapture "tags/*.html" . toLower)134 create ["sidebar"] $ do135 deps <- makePatternDependency KindMetadata (fromGlob "notes/*")136 compile $ do137 -- XXX: renderTagList/renderTags does not tell dependencies itself.138 -- It cannot do so properly as it does not receive a 'Pattern' and139 -- can thus not express the need for rebuilds on newly added pages.140 --141 -- https://github.com/jaspervdj/hakyll/blob/v4.16.7.1/lib/Hakyll/Web/Tags.hs#L183142 compilerTellDependencies [deps]143144 allTagsCtx <- constField "allTags" <$> renderTagList tags145 makeItem []146 >>= loadAndApplyTemplate "templates/sidebar.html" allTagsCtx147148 match "index.md" $ do149 route $ setExtension "html"150 compile $ do151 notes <- getMetadataItems (fromGlob "notes/*") >>= recentFirst152 sidebar <- constField "sidebar" <$> loadBody "sidebar"153154 pandocCompilerZk155 >>= loadAndApplyTemplate156 "templates/index.html"157 (notesField "notes" (take 5 notes) <> defaultContext)158 >>= saveSnapshot "content"159 >>= loadAndApplyTemplate "templates/default.html" (sidebar <> noteCtx)160 >>= relativizeUrls161162 match "notes/*" $ do163 route $ setExtension "html"164 compile $ do165 sidebar <- constField "sidebar" <$> loadBody "sidebar"166 let postTags = tagsField "tags" tags167168 pandocCompilerZk169 >>= loadAndApplyTemplate "templates/note.html" (postTags <> noteCtx)170 >>= saveSnapshot "content"171 >>= loadAndApplyTemplate "templates/default.html" (sidebar <> noteCtx)172 >>= relativizeUrls173174 tagsRules tags $ \tagStr tagsPattern -> do175 route idRoute176 compile $ do177 aside <- constField "sidebar" <$> loadBody "sidebar"178 notes <- getMetadataItems tagsPattern >>= recentFirst179180 let title = constField "title" tagStr181 baseCtx = title <> aside <> defaultContext182 listCtx = title <> notesField "notes" notes183184 makeItem []185 >>= loadAndApplyTemplate "templates/notes.html" listCtx186 >>= loadAndApplyTemplate "templates/default.html" baseCtx187 >>= relativizeUrls188189 match "templates/*" $ compile templateBodyCompiler190191--------------------------------------------------------------------------------192193dateCtx :: Context b194dateCtx =195 dateField "date" "%B %e, %Y"196 -- See https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/time#valid_datetime_values197 <> dateField "datetime" "%F"198199noteCtx :: Context String200noteCtx = dateCtx <> defaultContext201202notesField :: String -> [Item Metadata] -> Context String203notesField name notes =204 let fields = dateCtx <> urlField "url" <> metadataField205 in listField name fields (pure notes)