gdritter repos khuzd / master src / Templates.hs
master

Tree @master (Download .tar.gz)

Templates.hs @masterraw · history · blame

{-# LANGUAGE OverloadedStrings, RecordWildCards, BangPatterns #-}

module Templates(Page(..), errorPage) where

import Types

import           Data.Maybe (isJust)
import           Data.Monoid ((<>))
import           Data.Text (Text)
import qualified Data.Text as T
import           Data.Time (UTCTime(..))
import           Data.Time.Calendar (toGregorian)
import           Data.Time.Format (formatTime)
import           Prelude (String, ($), (++), (-), (==), return, Bool(..), Maybe(..), show)
import qualified Prelude as P
import           System.Locale (defaultTimeLocale)
import           Text.Blaze.Html5
import           Text.Blaze.Html5.Attributes hiding (title, form, span)
import           Text.Pandoc (writeHtml, readMarkdown, def)
import           Text.Pandoc.Options (WriterOptions(..))

-- A data representation of a page to be rendered
data Page
  = Index (Maybe Text) Post
  | List  (Maybe Text) [PostRef]
  | Entry (Maybe Text) Post
  | Edit  Text RawPost
  | LoginForm (Maybe Text)
  | PasswdForm Text

instance ToMarkup Page where
  toMarkup (Index lg post@(Post { .. })) =
    page lg postTitle (postBody lg post)
  toMarkup (List lg ps) =
    page lg "Past Entries" (listBody ps)
  toMarkup (Entry lg post@(Post { .. })) =
    page lg postTitle (postBody lg post)
  toMarkup (Edit uname rawPost) =
    page (Just uname) "Create Post" (editForm uname rawPost)
  toMarkup (PasswdForm uname) =
    page (Just uname) "Change Password" (passwdForm uname)
  toMarkup (LoginForm lg) =
    page lg "Log In" loginForm

page :: Maybe Text -> Text -> Html -> Html
page isLoggedIn pgName pgContents = docTypeHtml $ do
  head $ do
    meta ! charset "utf-8"
    link ! rel "stylesheet" ! type_ "text/css" ! href "/static/main.css"
    script ! src "/static/main.js" $ return ()
    title (toHtml ("Baruk Khazâd: " `T.append` pgName))
  body ! id "bg" $ do
    userText
    div ! class_ "title" $ h1 $
      span ! class_ "bilingual" 
           ! dataAttribute "english" "Axes of the Dwarves! The Dwarves are Upon You!"
           ! dataAttribute "dwarvish" "Baruk Khazâd! Khazâd ai-Mênu!"
           $ ("Baruk Khazâd! Khazâd ai-Mênu!")
    div ! class_ "nav" $ titlebar isLoggedIn
    div ! class_ "main" $ pgContents
  where userText = case isLoggedIn of
                     Just user -> div ! class_ "username" $ do
                                    span ! class_ "msg" $ toMarkup ("Logged in as " <> user)
                                    " — "
                                    span ! class_ "lnk" $ do
                                      a ! href "/change" $ "Change My Password"
                                    " — "
                                    span ! class_ "lnk" $ do
                                      a ! href "/logout" $ "Log Out"
                     Nothing   -> return ()

errorPage :: Text -> Text -> Html
errorPage err desc = page Nothing ("Error: " <> err ) $ do
  div ! class_ "errmsg" $ toMarkup desc

passwdForm :: Text -> Html
passwdForm uname = do
  form ! name "passwd"
       ! action "/change"
       ! method "POST"
       ! enctype "application/x-www-form-urlencoded;charset=UTF-8" $ do
    toMarkup ("Old password for " <> uname <> ": ")
    br
    input ! type_ "password" ! name "oldpasswd"
    br
    "New password: "
    br
    input ! type_ "password" ! name "p1"
    br
    input ! type_ "password" ! name "p2"
    br
    input ! type_ "submit"

loginForm :: Html
loginForm = div ! class_ "login" $ do
  form ! name "login"
       ! action "/login"
       ! method "POST"
       ! enctype "application/x-www-form-urlencoded;charset=UTF-8" $ do
    input ! type_ "text" ! name "user"
    br
    input ! type_ "password" ! name "passwd"
    br
    input ! type_ "submit"

editForm :: Text -> RawPost -> Html
editForm uname (RawPost { .. }) = div ! class_ "edit" $ do
    form ! name "newpost"
         ! action "/"
         ! method "POST"
         ! enctype "application/x-www-form-urlencoded;charset=UTF-8" $ do
      let idVal = case rpId of
                    Nothing -> toValue ("none" :: String)
                    Just n  -> toValue n
      input ! type_ "hidden" ! name "id" ! value idVal
      "Title: "
      input ! type_ "text" ! name "title" ! value (toValue rpTitle)
      input ! type_ "hidden" ! name "author" ! value (toValue uname)
      br
      textarea ! cols "80" ! rows "40" ! name "contents" $ toHtml rpContents
      br
      input ! type_ "submit"

titlebar :: Maybe Text -> Html
titlebar user = P.mapM_ go links
  where go (lname, url) = a ! class_ "navitem" ! href url $ lname
        links = if isJust user then
                  [ ("Newest",  "/newest")
                  , ("Archive", "/archive")
                  , ("Create",  "/create")
                  , ("Oldest",  "/oldest")
                  ]
                else
                  [ ("Newest",  "/newest")
                  , ("Archive", "/archive")
                  , ("Log In",  "/auth")
                  , ("Oldest",  "/oldest")
                  ]

postLink :: PostRef -> Html
postLink (post@PostRef { .. }) =
  a ! href (toValue (urlFor post)) $ toHtml prName

postBody :: Maybe Text -> Post -> Html
postBody user (post@Post { .. }) = div ! class_ "post" $ do
  h2 (toHtml postTitle)
  div ! class_ "author" $ toHtml postAuthor

  let htmlOpts = def { writerHtml5 = True }
  let convPost = T.replace "\r\n" "\n" postContents
  writeHtml htmlOpts (readMarkdown def (T.unpack convPost))
  editLink user
  div ! class_ "new" $ maybeLink postNext "Newer"
  div ! class_ "old" $ maybeLink postPrev "Older"
    where maybeLink Nothing   _ = return ()
          maybeLink (Just pr) n = postLink (pr { prName = n })
          editLink (Just uname)
            | uname == postAuthor = a ! href editURL $ "Edit this post"
          editLink _ = return ()
          editURL = toValue (urlForPost post <> "/edit")

toMonth :: P.Int -> Html
toMonth n = span ! class_ "bilingual"
                 ! dataAttribute "english" ename
                 ! dataAttribute "dwarvish" dname
                 $ return ()
  where ename = [ "January", "February", "March"
                , "April",   "May",      "June"
                , "July",    "August",   "September"
                , "October", "November", "December"
                ] P.!! n
        dname = [ "Granite",   "Slate",     "Felsite"
                , "Hematite",  "Malachite", "Galena"
                , "Limestone", "Sandstone", "Timber"
                , "Moonstone", "Opal",      "Obsidian"
                ] P.!! n

formatDate :: UTCTime -> Html
formatDate t = do
  toMarkup (show day)
  " "
  toMonth (month - 1)
  ", "
  toMarkup (show year)
    where (year, month, day) = toGregorian (utctDay t)

listBody :: [PostRef] -> Html
listBody ps = div ! class_ "list" $ P.mapM_ go ps
  where go ref = do
          p $ do
            postLink ref
            span ! class_ "date" $ do
              " on "
              toMarkup (formatDate (prDate ref))