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/*" $ do168 route $ setExtension "html"169 compile $ do170 sidebar <- constField "sidebar" <$> loadBody "sidebar"171 let postTags = tagsField "tags" tags172173 pandocCompilerZk174 >>= loadAndApplyTemplate "templates/note.html" (postTags <> noteCtx)175 >>= saveSnapshot "content"176 >>= loadAndApplyTemplate "templates/default.html" (sidebar <> noteCtx)177 >>= relativizeUrls178179 tagsRules tags $ \tagStr tagsPattern -> do180 route idRoute181 compile $ do182 aside <- constField "sidebar" <$> loadBody "sidebar"183 notes <- getMetadataItems tagsPattern >>= recentFirst184185 let title = constField "title" tagStr186 baseCtx = title <> aside <> defaultContext187 listCtx = title <> notesField "notes" notes188189 makeItem []190 >>= loadAndApplyTemplate "templates/notes.html" listCtx191 >>= loadAndApplyTemplate "templates/default.html" baseCtx192 >>= relativizeUrls193194 match "templates/*" $ compile templateBodyCompiler195196--------------------------------------------------------------------------------197198dateCtx :: Context b199dateCtx =200 dateField "date" "%B %e, %Y"201 -- See https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/time#valid_datetime_values202 <> dateField "datetime" "%F"203204noteCtx :: Context String205noteCtx = dateCtx <> defaultContext206207notesField :: String -> [Item Metadata] -> Context String208notesField name notes =209 let fields = dateCtx <> urlField "url" <> metadataField210 in listField name fields (pure notes)