1 {-# LANGUAGE OverloadedStrings #-}
5 import qualified Data.Text.Lazy as T
6 import qualified Data.Text.Lazy.IO as T
11 import Control.Monad.IO.Class
13 import qualified Text.MMark as M
14 import qualified Text.MMark.Extension.Common as M
18 import System.Directory
19 import System.FilePath
20 import Network.HTTP.Types
23 main = scotty 3000 $ do
24 get "/style.css" $ file "style.css"
26 get "/create" $ html $ renderText $ html_ $ do
31 h1_ "Create a new post"
32 with form_ [action_ "create", method_ "post"] $ do
33 input_ [name_ "title", type_ "text", placeholder_ "Title"]
35 with textarea_ [name_ "body", placeholder_ "# Your markdown here"] ""
37 input_ [type_ "submit"]
42 p <- liftIO $ save b t
43 redirect ("post/" <> title p)
46 username <- param "username"
47 password <- param "password"
48 if username /= ("luke" :: T.Text) || password /= ("pass" :: T.Text)
49 then status unauthorized401
50 else redirect "/create"
54 names <- listDirectory "posts"
55 let files = ("posts" </>) <$> names
56 posts <- catMaybes <$> mapM load files
57 return $ take 5 $ sortOn (Down . date) posts
58 html $ renderText $ html_ $ do
59 head_ $ style >> title_ "Cool Blog"
61 p_ $ a_ [href_ "/"] "Cool Blog"
62 with form_ [action_ "/login", method_ "post"] $ do
63 input_ [name_ "username", placeholder_ "Username", type_ "text"]
64 input_ [name_ "password", placeholder_ "Password", type_ "password"]
65 input_ [type_ "submit"]
66 sequence (mapMaybe render posts)
68 get "/posts/:post" $ do
70 mPost <- liftIO $ load ("posts" </> name <.> "md")
72 Nothing -> status notFound404
75 Nothing -> status internalServerError500
77 html $ renderText $ html_ $ do
78 head_ $ style >> title_ "Post"
80 p_ $ a_ [href_ "/"] "Home"
84 style = link_ [Attribute "rel" "stylesheet", Attribute "href" "/style.css"]
92 load :: FilePath -> IO (Maybe Post)
94 liftIO $ guard (takeExtension file == ".md")
95 md <- liftIO (T.readFile file)
96 modTime <- getModificationTime file
97 let title = T.pack $ takeBaseName file
98 return $ Just (Post md title modTime)
100 render :: Post -> Maybe (Html ())
101 render p@(Post markdown title date) =
102 case M.parse (pathToPost p) (T.toStrict markdown) of
105 with div_ [id_ (T.toStrict title)] $ do
106 with a_ [href_ (T.toStrict $ linkToPost p)] $
107 i_ $ toHtml $ formatTime defaultTimeLocale "%a %e %B %Y" date
109 M.render (M.useExtensions extensions doc)
110 where extensions = [M.ghcSyntaxHighlighter, M.skylighting, M.footnotes]
112 linkToPost :: Post -> T.Text
113 linkToPost p = T.pack $ "/posts" </> T.unpack (title p)
115 pathToPost :: Post -> FilePath
116 pathToPost p = "posts" </> T.unpack (title p) <.> "md"
118 save :: T.Text -> T.Text -> IO Post
120 curTime <- getCurrentTime
121 let kTitle = T.pack $ kebab $ T.unpack title
122 p = Post md kTitle curTime
123 T.writeFile (pathToPost p) (markdown p)