module Dixi.Markup where
import Control.Lens
import Data.Foldable (toList)
import Data.Maybe (fromMaybe)
import Data.Monoid
import Data.Patch (Hunks, HunkStatus(..))
import Data.Proxy
import Data.Text (Text)
import Servant.API
import Servant.HTML.Blaze
import Text.Blaze
import Text.Blaze.Renderer.Utf8 (renderMarkup)
import Text.Hamlet (shamlet, Html)
import Text.Heredoc
import Text.Lucius
import Text.Pandoc.Error
import qualified Data.Text as T
import qualified Data.Text.Lazy as L
import qualified Data.ByteString.Lazy as B
import Dixi.API
import Dixi.Common
import Dixi.Config
import Dixi.Page
import Dixi.Hamlet
import Dixi.PatchUtils
link :: (IsElem endpoint Dixi, HasLink endpoint) => Proxy endpoint -> MkLink endpoint
link = safeLink dixi
renderTitle :: Text -> Text
renderTitle = T.pack . map (\c -> if c == '_' then ' ' else c) . T.unpack
prettyUrl :: Proxy ( Capture "page" Key :> "history"
:> Capture "version" Version
:> Get '[HTML, JSON] PrettyPage
)
prettyUrl = Proxy
latestUrl :: Proxy (Capture "page" Key :> Get '[HTML, JSON] PrettyPage)
latestUrl = Proxy
rawUrl :: Proxy ( Capture "page" Key :> "history"
:> Capture "version" Version
:> "raw" :> Get '[HTML, JSON] RawPage
)
rawUrl = Proxy
amendUrl :: Proxy ( Capture "page" Key :> "history"
:> Capture "version" Version
:> ReqBody '[FormUrlEncoded, JSON] NewBody
:> Post '[HTML, JSON] PrettyPage
)
amendUrl = Proxy
diffUrl :: Proxy (Capture "page" Key :> "history" :> "diff" :> Get '[HTML, JSON] DiffPage)
diffUrl = Proxy
historyUrl :: Proxy (Capture "page" Key :> "history" :> Get '[HTML, JSON] History)
historyUrl = Proxy
revertUrl :: Proxy (Capture "page" Key :> "history" :> "revert" :> ReqBody '[FormUrlEncoded, JSON] RevReq :> Post '[HTML, JSON] PrettyPage)
revertUrl = Proxy
outerMatter :: Html -> Text -> Html -> Html
outerMatter ss title bod = [shamlet|
$doctype 5
<html>
<head>
<link href="http://fonts.googleapis.com/css?family=PT+Serif:400,700" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
#{ss}
<title> #{title}
<body>
<div .header> #{title}
#{bod}
|]
unlast :: a -> Last a -> a
unlast d (Last x) = fromMaybe d x
guardText :: Text -> Text -> Text
guardText x y | y == "" = x
| otherwise = y
dixiError :: Html -> DixiError -> B.ByteString
dixiError header (VersionNotFound k v) = renderMarkup $ outerMatter header (renderTitle k)
[shamlet|
#{pageHeader k "Error"}
<div .body>
<h1> Error
<span.error> Version #{v} not found!
|]
dixiError header (PatchNotApplicable k) = renderMarkup $ outerMatter header (renderTitle k)
[shamlet|
#{pageHeader k "Error"}
<div .body>
<h1> Internal Error
<span.error> Patch not Applicable!
|]
instance ToMarkup URI where
toMarkup u = [shamlet|#{show u}|]
instance ToMarkup PatchSummary where
toMarkup (i,d,r) = [hml|
<span .fa .faplussquareo .additionsum> #{show i}
<span .fa .faminussquareo .subtractionsum> #{show d}
<span .fa .fapencilsquareo .replacementsum> #{show r}
|]
instance ToMarkup DiffPage where
toMarkup (DP (Renders {..}) k v1 v2 p) = outerMatter headerBlock (renderTitle k) $ [shamlet|
#{pageHeader k vString}
<div .body>
<div>
#{renderHunks d}
<br>
<hr>
<form method="POST" action="/#{link revertUrl k}">
<input type="hidden" name="from" value="#{show v1}">
<input type="hidden" name="to" value="#{show v2}">
<input type="text" name="comment" value="revert #{show v1} - #{show v2}">
<button type="submit">
<span .fa .faundo> Revert
|]
where
d = p ^. body
renderHunks :: Hunks Char -> Html
renderHunks ps = [hml|
$forall (x, s) <- ps
<span class="hunk #{styleFor s}">#{toList x}
|]
styleFor :: HunkStatus -> String
styleFor Inserted = "hunk-inserted"
styleFor Deleted = "hunk-deleted"
styleFor Replaced = "hunk-replaced"
styleFor Unchanged = "hunk-unchanged"
vString :: Text
vString = "diff " <> T.pack (show v1) <> " - " <> T.pack (show v2)
instance ToMarkup History where
toMarkup (H (Renders {..}) k []) = outerMatter headerBlock (renderTitle k) $ pageHeader k "history"
toMarkup (H (Renders {..}) k ps) = outerMatter headerBlock (renderTitle k) $ [shamlet|
#{pageHeader k "history"}
<div .body>
<form method="GET" action="/#{link diffUrl k}">
<table .history>
<tr>
<th .histhversion> Version
<th .histhfromto> From/To
<th .histhchanges> Changes
<th .histhcomment> Comment
<th .histhcomment> Time
$forall (v, p) <- ps'
<tr>
<td .histversion>
#{show v}.
<td .histfromto>
<input type="radio" checked style="position:relative; top:1em;" name="from" value="#{show (v - 1)}">
<input type="radio" checked name="to" value="#{show v}">
<td>
#{(p ^. body)}
<td>
<a .histlink href="/#{link prettyUrl k v}">#{guardText "no comment" (unlast "no comment" (p ^. comment))}
<td>
<span .timestamp>#{renderTime (p ^. time)}
<tr>
<td>
<tr>
<td>
<td>
<button type="submit">
<span .fa .fafileso>
\ Diff
<td>
<td>
<small> (to revert a change, view the diff first)
|]
where ps' = reverse $ zip [1..] ps
versionHeader :: Key -> Version -> Text -> Html
versionHeader k v com = [shamlet|
<div .subtitle>
version #{v} (#{com'})
<div .toolbar>
<a href="/#{link rawUrl k v}" .fa .faedit> edit
<a href="/#{link prettyUrl k v}" .fa .faeye> view
<a href="/#{link historyUrl k}" .fa .fahistory> history
<a href="/#{link latestUrl k}" .fa .fafastforward> latest
|]
where com' = if com == "" then "no comment" else com
pageHeader :: Key -> Text -> Html
pageHeader k com = [shamlet|
<div .subtitle>
#{com}
<div .toolbar>
<a href="/#{link historyUrl k}" .fa .fahistory> history
<a href="/#{link latestUrl k}" .fa .fafastforward> latest
|]
instance ToMarkup PandocError where
toMarkup (ParseFailure s) = [shamlet| <b> Parse Failure: </b> #{s}|]
toMarkup (ParsecError _ e) = [shamlet| <b> Parse Error: </b> #{show e} |]
writePandocError :: PandocError -> Html
writePandocError err = [shamlet|#{err}|]
instance ToMarkup PrettyPage where
toMarkup (PP (Renders {..}) k v p)
= let
com = p ^. comment . traverse
tim = renderTime $ p ^. time
in outerMatter headerBlock (renderTitle k)
[shamlet|
#{versionHeader k v com}
<div .body>
#{p ^. body}
<div .timestamp> This version was last edited at #{tim}
|]
instance ToMarkup RawPage where
toMarkup (RP (Renders {..}) k v p )
= let
com = p ^. comment . traverse
bod = p ^. body
in outerMatter headerBlock (renderTitle k)
[shamlet|
#{versionHeader k v com}
<div .body>
<form method="POST" action="/#{link amendUrl k v}">
<textarea name="content" cols=80 rows=24 style="font-family:monospace">#{bod}
<br>
<input type="text" name="comment" value="no comment">
<input type="submit">
|]
defaultStylesheet :: L.Text
Right defaultStylesheet = luciusRT [here|
div.body {
margin: 1em;
}
table.history {
border: 0px;
td {
border: 0px;
button {
width: 100%;
padding: 4px;
}
}
tr {
border: 0px;
}
}
.histversion {
textalign:right;
}
.histhcomment {
textalign:left;
}
.histhversion {
paddingright:5px;
}
.histfromto {
textalign:center;
}
body {
fontfamily: PT Serif, Palatino, Georgia, Times, serif;
margin: 0px;
}
.toolbar {
background: #BBBBAA;
bordertop: 1px solid #888877;
borderbottom: 1px solid #EEEEDD;
a:hover {
background: #F1F1D9;
border: 1px outset #F1F1D9;
}
a:active {
background: #F1F1D9;
border: 1px inset #F1F1D9;
}
a {
background: #DCDCCB;
border: 1px outset #F1F1D9;
textdecoration: none;
color: black;
padding: 2px;
margintop: 2px;
marginbottom: 2px;
marginleft: 2px;
}
}
.header {
background: #FFFFDD;
fontsize: 1.5em;
fontweight: bold;
paddingleft: 0.5em;
paddingtop: 0.5em;
paddingbottom: 0.5em;
}
.subtitle {
float:right;
fontsize: 0.8em;
marginright: 0.5em;
color: gray;
position: relative;
top: 2.5em;
}
.additionsum {
background: #B5F386;
padding: 3px;
borderradius: 6px 0px 0px 6px;
margintop:1px;
marginbottom:1px;
}
.subtractionsum {
background: #EC8160;
padding: 3px;
margintop:1px;
marginbottom:1px;
}
.replacementsum {
background: #F3E686;
padding: 3px;
borderradius: 0px 6px 6px 0px;
margintop:1px;
marginbottom:1px;
}
.hunk {
whitespace: pre;
fontfamily:monospace;
borderradius: 4px;
}
.hunkinserted {
background: #B5F386;
}
.hunkdeleted {
background: #EC8160;
textdecoration: linethrough;;
}
.hunkreplaced {
background: #F3E686;
}
.timestamp {
color: #444444;
fontsize: small;
}
div.timestamp {
marginleft: 0.5em;
margintop: 2em;
}
|] []