1{-# LANGUAGE OverloadedStrings #-}2{-# LANGUAGE ViewPatterns #-}3{-# LANGUAGE CPP #-}45import Data.Maybe (fromJust)6import qualified Data.Char as Char7import qualified Data.Text as T8import Hakyll hiding (renderTagList)9import Hakyll.Core.Compiler.Internal (compilerTellDependencies)10import Hakyll.Core.Dependencies11import System.FilePath (replaceExtension, takeExtension)12import Text.Blaze ((!))13import Text.Blaze.Html.Renderer.String (renderHtml)14import qualified Text.Blaze.Html5 as H15import qualified Text.Blaze.Html5.Attributes as A16import qualified Text.Pandoc as P17import Text.Pandoc.Highlighting (Style, haddock, styleToCss)18import Text.Pandoc.Walk (walk)1920toLower :: String -> String21toLower = fmap Char.toLower2223------------------------------------------------------------------------2425linkToTag :: T.Text -> P.Inline26linkToTag name =27 let desc = "All pages tagged '" `T.append` name `T.append` "'"28 file = "/tags/" `T.append` T.toLower name `T.append` ".html"29 in P.Link ("", [], []) [P.Str name] (file, desc)3031-- Transform zk references to .md files to .html files.32--33-- TODO: Filter out references to private notes.34-- TODO: Support note references with anchors (e.g., for referencing a section).35fixupNoteRefs :: Item String -> Compiler (Item String)36fixupNoteRefs = pure . fmap (withUrls go)37 where38 go :: String -> String39 go url40 | isZkRef url = replaceExtension url ".html"41 | otherwise = url4243 -- Returns true if the URL is a reference to another zk note.44 isZkRef :: String -> Bool45 isZkRef ('#' : _) = False46 isZkRef url =47 let ext = takeExtension url48 in not (isExternal url) && (ext == "" || ext == ".md")4950-- Implements support for tags using Bear's multi-word tag syntax.51--52-- See:53-- • https://github.com/zk-org/zk/blob/dev/docs/notes/tags.md>54-- • https://github.com/zk-org/zk/blob/v0.15.1/internal/adapter/markdown/extensions/tag.go#L7955inlineBearTags :: [P.Inline] -> [P.Inline]56inlineBearTags (i@(P.Str (T.stripPrefix "#" -> Just tagRst)) : ix) =57 case takeTagElems (P.Str tagRst : ix) of58 Nothing -> i : inlineBearTags ix59 Just el ->60 let (tag, rst) = splitTag $ T.unwords el61 numElement = (length el - 1) * 2 -- count P.Space too62 in [linkToTag tag, P.Str rst] ++ inlineBearTags (drop numElement ix)63 where64 takeTagElems :: [P.Inline] -> Maybe [T.Text]65 takeTagElems (P.Str str : xs)66 | T.elem '#' str = Just [str]67 | otherwise = (str :) <$> takeTagElems xs68 takeTagElems (P.Space : xs) = takeTagElems xs69 takeTagElems _ = Nothing7071 splitTag :: T.Text -> (T.Text, T.Text)72 splitTag t = splitAtEx (fromJust $ T.findIndex (== '#') t) t7374 -- Like T.splitAt because exclude the seperator in 'snd'.75 splitAtEx :: Int -> T.Text -> (T.Text, T.Text)76 splitAtEx n t = let (b, a) = T.splitAt n t in (b, T.drop 1 a)77inlineBearTags (i : ix) = i : inlineBearTags ix78inlineBearTags [] = []7980pandocCodeStyle :: Style81pandocCodeStyle = haddock8283pandocCompilerZk :: Compiler (Item String)84pandocCompilerZk =85 cached "pandocCompilerZk" $86 (pandocCompilerZk' >>= fixupNoteRefs)87 where88 pandocCompilerZk' :: Compiler (Item String)89 pandocCompilerZk' =90 pandocCompilerWithTransform91 defaultHakyllReaderOptions92 { P.readerStripComments = True }93 defaultHakyllWriterOptions94#if MIN_VERSION_pandoc(3,8,0)95 { P.writerHighlightMethod = P.Skylighting pandocCodeStyle }96#else97 { P.writerHighlightStyle = Just pandocCodeStyle }98#endif99 (walk transform)100101 transform :: P.Block -> P.Block102 transform = walk inlineBearTags103104------------------------------------------------------------------------105106getMetadataItems :: Pattern -> Compiler [Item Metadata]107getMetadataItems pattern = map (uncurry Item) <$> getAllMetadata pattern108109-- Custom version of Hakyll's renderTagList.110-- TODO: Maybe produce a Context here.111renderTagList :: Tags -> Compiler String112renderTagList = renderTags makeLink concat113 where114 makeLink tag url count _minCount _maxCount =115 renderHtml . H.li116 $ H.a117 ! A.href (H.toValue url)118 ! A.class_ "tag"119 ! A.title ("Navigate posts by tag '" <> H.stringValue tag <> "'")120 $ H.toHtml (tag ++ " (" ++ show count ++ ")")121122------------------------------------------------------------------------123124config :: Configuration125config = defaultConfiguration126 { deployCommand = "./deploy.sh" }127128main :: IO ()129main = hakyllWith config $ do130 match "css/*" $ do131 route idRoute132 compile compressCssCompiler133 create ["css/syntax.css"] $ do134 route idRoute135 compile $136 makeItem $ (compressCss $ styleToCss pandocCodeStyle)137138 tags <- buildTags "notes/*" (fromCapture "tags/*.html" . toLower)139 create ["sidebar"] $ do140 deps <- makePatternDependency KindMetadata (fromGlob "notes/*")141 compile $ do142 -- XXX: renderTagList/renderTags does not tell dependencies itself.143 -- It cannot do so properly as it does not receive a 'Pattern' and144 -- can thus not express the need for rebuilds on newly added pages.145 --146 -- https://github.com/jaspervdj/hakyll/blob/v4.16.7.1/lib/Hakyll/Web/Tags.hs#L183147 compilerTellDependencies [deps]148149 allTagsCtx <- constField "allTags" <$> renderTagList tags150 makeItem []151 >>= loadAndApplyTemplate "templates/sidebar.html" allTagsCtx152153 match "index.md" $ do154 route $ setExtension "html"155 compile $ do156 notes <- getMetadataItems (fromGlob "notes/*") >>= recentFirst157 sidebar <- constField "sidebar" <$> loadBody "sidebar"158159 pandocCompilerZk160 >>= loadAndApplyTemplate161 "templates/index.html"162 (notesField "notes" (take 5 notes) <> defaultContext)163 >>= saveSnapshot "content"164 >>= loadAndApplyTemplate "templates/default.html" (sidebar <> noteCtx)165 >>= relativizeUrls166167 match "notes/img/*" $ do168 route idRoute169 compile copyFileCompiler170171 match "notes/*" $ do172 route $ setExtension "html"173 compile $ do174 sidebar <- constField "sidebar" <$> loadBody "sidebar"175 let postTags = tagsField "tags" tags176177 pandocCompilerZk178 >>= loadAndApplyTemplate "templates/note.html" (postTags <> noteCtx)179 >>= saveSnapshot "content"180 >>= loadAndApplyTemplate "templates/default.html" (sidebar <> noteCtx)181 >>= relativizeUrls182183 tagsRules tags $ \tagStr tagsPattern -> do184 route idRoute185 compile $ do186 aside <- constField "sidebar" <$> loadBody "sidebar"187 notes <- getMetadataItems tagsPattern >>= recentFirst188189 let title = constField "title" tagStr190 baseCtx = title <> aside <> defaultContext191 listCtx = title <> notesField "notes" notes192193 makeItem []194 >>= loadAndApplyTemplate "templates/notes.html" listCtx195 >>= loadAndApplyTemplate "templates/default.html" baseCtx196 >>= relativizeUrls197198 match "templates/*" $ compile templateBodyCompiler199200--------------------------------------------------------------------------------201202dateCtx :: Context b203dateCtx =204 dateField "date" "%B %e, %Y"205 -- See https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/time#valid_datetime_values206 <> dateField "datetime" "%F"207208noteCtx :: Context String209noteCtx = dateCtx <> defaultContext210211notesField :: String -> [Item Metadata] -> Context String212notesField name notes =213 let fields = dateCtx <> urlField "url" <> metadataField214 in listField name fields (pure notes)