nirum-0.5.0: src/Nirum/Docs/Html.hs
{-# LANGUAGE OverloadedStrings, QuasiQuotes #-}
module Nirum.Docs.Html
( render
, renderBlock
, renderInline
, renderInlines
, renderLinklessInlines
) where
import Data.List.NonEmpty
import Prelude hiding (head, zip)
import qualified Data.Text as T
import Text.InterpolatedString.Perl6 (qq)
import Nirum.Docs
renderInline :: Inline -> Html
renderInline (Text t) = escape t
renderInline SoftLineBreak = "\n"
renderInline HardLineBreak = "<br>"
renderInline (HtmlInline html) = html
renderInline (Code code') = [qq|<code>{escape code'}</code>|]
renderInline (Emphasis inlines) = [qq|<em>{renderInlines inlines}</em>|]
renderInline (Strong inlines) = [qq|<strong>{renderInlines inlines}</strong>|]
renderInline (Link url title inlines) =
let body = renderInlines inlines
in
if T.null title
then [qq|<a href="{escape url}">$body</a>|]
else [qq|<a href="{escape url}" title="{escape title}">$body</a>|]
renderInline (Image url title) =
if T.null title
then [qq|<img src="{escape url}">|]
else [qq|<img src="{escape url}" title="{escape title}">|]
escape :: T.Text -> Html
escape = T.concatMap escapeChar
escapeChar :: Char -> Html
escapeChar '&' = "&"
escapeChar '"' = """
escapeChar '<' = "<"
escapeChar '>' = ">"
escapeChar c = T.singleton c
renderInlines :: [Inline] -> Html
renderInlines = T.concat . fmap renderInline
renderLinklessInlines :: [Inline] -> Html
renderLinklessInlines inlines = T.concat
[ case i of
Link _ _ inlines' -> renderInlines inlines'
i' -> renderInline i'
| i <- inlines
]
renderBlock :: Block -> Html
renderBlock (Document blocks) = renderBlocks blocks `T.snoc` '\n'
renderBlock ThematicBreak = "<hr>"
renderBlock (Paragraph inlines) = [qq|<p>{renderInlines inlines}</p>|]
renderBlock (BlockQuote blocks) =
[qq|<blockquote>{renderBlocks blocks}</blockquotes>|]
renderBlock (HtmlBlock html) = html
renderBlock (CodeBlock lang code') =
if T.null lang
then [qq|<pre><code>$escapedCode</code></pre>|]
else [qq|<pre><code class="language-$lang">$escapedCode</code></pre>|]
where
escapedCode :: Html
escapedCode = escape code'
renderBlock (Heading level inlines anchorId) =
let lv = headingLevelInt level
id' = case anchorId of
Nothing -> ""
Just aid -> [qq| id="$aid"|] :: T.Text
in [qq|<h$lv$id'>{renderInlines inlines}</h$lv>|]
renderBlock (List listType itemList) =
let liList = case itemList of
TightItemList items ->
[ [qq|<li>{renderTightBlocks item}</li>|]
| item <- items
]
LooseItemList items ->
[ [qq|<li>{renderBlocks item}</li>|]
| item <- items
]
tag = case listType of
BulletList -> "ul" :: T.Text
OrderedList { startNumber = 1 } -> "ol"
OrderedList { startNumber = startNumber' } ->
[qq|ol start="$startNumber'"|]
nl = '\n'
liListT = T.intercalate "\n" liList
in [qq|<$tag>$nl$liListT$nl</$tag>|]
renderBlock (Table columns rows) =
[qq|<table>$lf<thead>$lf<tr>
{T.concat (toList $ fmap th $ zip columns (head rows))}
</tr>$lf</thead>
<tbody>{T.concat (fmap tr $ Data.List.NonEmpty.tail rows)}</tbody></table>|]
where
lf :: Char
lf = '\n'
th :: (TableColumn, TableCell) -> Html
th (col, cell) = [qq|$lf<th{align col}>{renderInlines cell}</th>|]
align :: TableColumn -> Html
align NotAligned = ""
align LeftAligned = " align=\"left\""
align CenterAligned = " align=\"center\""
align RightAligned = " align=\"right\""
tr :: TableRow -> Html
tr cells = [qq|$lf<tr>{T.concat (toList $ fmap td cells)}</tr>|]
td :: TableCell -> Html
td inlines = [qq|$lf<td>{renderInlines inlines}</td>|]
renderBlocks :: [Block] -> Html
renderBlocks = T.intercalate "\n" . fmap renderBlock
renderTightBlocks :: [Block] -> Html
renderTightBlocks blocks = T.intercalate "\n"
[ case b of
Paragraph inlines -> renderInlines inlines
b' -> renderBlock b'
| b <- blocks
]
render :: Block -> Html
render = renderBlock