{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralisedNewtypeDeriving #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
module Reflex.Dom.Pandoc.Document
( elPandoc,
elPandocInlines,
elPandocBlocks,
PandocBuilder,
PandocRaw (..),
URILink (..),
Config (..),
defaultConfig,
)
where
import Control.Monad
import Control.Monad.Reader
import Data.Bool
import qualified Data.Map as Map
import qualified Data.Text as T
import Reflex.Dom.Core hiding (Link, Space, mapAccum)
import Reflex.Dom.Pandoc.Footnotes
import Reflex.Dom.Pandoc.PandocRaw
import Reflex.Dom.Pandoc.SyntaxHighlighting (elCodeHighlighted)
import Reflex.Dom.Pandoc.URILink
import Reflex.Dom.Pandoc.Util (elPandocAttr, headerElement, renderAttr, sansEmptyAttrs)
import Text.Pandoc.Definition
type PandocBuilder t m =
( DomBuilder t m,
PandocRaw m,
PandocRawConstraints m
)
data Config t m a = Config
{
_config_renderURILink :: m a -> URILink -> m a
}
defaultConfig :: Monad m => Config t m ()
defaultConfig =
Config $ \f _ -> f >> pure ()
elPandoc :: forall t m a. (PandocBuilder t m, Monoid a) => Config t m a -> Pandoc -> m a
elPandoc cfg doc@(Pandoc _meta blocks) = do
divClass "pandoc" $ do
let fs = queryFootnotes doc
x <- flip runReaderT fs $ renderBlocks cfg blocks
fmap (x <>) $ renderFootnotes (sansFootnotes . renderBlocks cfg) fs
elPandocInlines :: PandocBuilder t m => [Inline] -> m ()
elPandocInlines = void . sansFootnotes . renderInlines defaultConfig
elPandocBlocks :: PandocBuilder t m => [Block] -> m ()
elPandocBlocks = void . sansFootnotes . renderBlocks defaultConfig
mapAccum :: (Monoid b, Applicative f) => (a -> f b) -> [a] -> f b
mapAccum f =
fmap mconcat . traverse f
renderBlocks :: (PandocBuilder t m, Monoid a) => Config t m a -> [Block] -> ReaderT Footnotes m a
renderBlocks cfg =
mapAccum $ renderBlock cfg
renderBlock :: (PandocBuilder t m, Monoid a) => Config t m a -> Block -> ReaderT Footnotes m a
renderBlock cfg = \case
Plain (Str "☐" : Space : is) -> checkboxEl False >> renderInlines cfg is
Plain (Str "☒" : Space : is) -> checkboxEl True >> renderInlines cfg is
Para (Str "☐" : Space : is) -> checkboxEl False >> renderInlines cfg is
Para (Str "☒" : Space : is) -> checkboxEl True >> renderInlines cfg is
Plain xs ->
renderInlines cfg xs
Para xs ->
el "p" $ renderInlines cfg xs
LineBlock xss ->
flip mapAccum xss $ \xs -> do
renderInlines cfg xs <* text "\n"
CodeBlock attr x ->
elCodeHighlighted attr x >> pure mempty
RawBlock fmt x ->
elPandocRaw fmt x >> pure mempty
BlockQuote xs ->
el "blockquote" $ renderBlocks cfg xs
OrderedList (idx, style, _delim) xss ->
elAttr "ol" (listStyle style <> startFrom idx) $ do
flip mapAccum xss $ \xs -> do
el "li" $ renderBlocks cfg xs
BulletList xss ->
el "ul" $ flip mapAccum xss $ \xs -> el "li" $ renderBlocks cfg xs
DefinitionList defs ->
el "dl" $
flip mapAccum defs $ \(term, descList) -> do
x <- el "dt" $ renderInlines cfg term
fmap (x <>) $
flip mapAccum descList $ \desc ->
el "dd" $ renderBlocks cfg desc
Header level attr xs ->
elPandocAttr (headerElement level) attr $ do
renderInlines cfg xs
HorizontalRule ->
el "hr" blank >> pure mempty
Table _attr _captions _colSpec (TableHead _ hrows) tbodys _tfoot -> do
elClass "table" "ui celled table" $ do
x <- el "thead" $ do
flip mapAccum hrows $ \(Row _ cells) -> do
el "tr" $ do
flip mapAccum cells $ \(Cell _ _ _ _ blks) ->
el "th" $ renderBlocks cfg blks
fmap (x <>) $
flip mapAccum tbodys $ \(TableBody _ _ _ rows) ->
el "tbody" $ do
flip mapAccum rows $ \(Row _ cells) ->
el "tr" $ do
flip mapAccum cells $ \(Cell _ _ _ _ blks) ->
el "td" $ renderBlocks cfg blks
Div attr xs ->
elPandocAttr "div" attr $
renderBlocks cfg xs
Null ->
blank >> pure mempty
where
checkboxEl checked = do
let attrs =
( mconcat $
[ "type" =: "checkbox",
"disabled" =: "True",
bool mempty ("checked" =: "True") checked
]
)
invisibleChar = "\8206"
divClass "ui disabled fitted checkbox" $ do
void $ elAttr "input" attrs blank
el "label" $ text invisibleChar
startFrom idx = bool mempty ("start" =: (T.pack $ show idx)) (idx /= 1)
listStyle = \case
LowerRoman -> "type" =: "i"
UpperRoman -> "type" =: "I"
LowerAlpha -> "type" =: "a"
UpperAlpha -> "type" =: "A"
_ -> mempty
renderInlines :: (PandocBuilder t m, Monoid a) => Config t m a -> [Inline] -> ReaderT Footnotes m a
renderInlines cfg =
mapAccum $ renderInline cfg
renderInline :: (PandocBuilder t m, Monoid a) => Config t m a -> Inline -> ReaderT Footnotes m a
renderInline cfg = \case
Str x ->
text x >> pure mempty
Emph xs ->
el "em" $ renderInlines cfg xs
Strong xs ->
el "strong" $ renderInlines cfg xs
Underline xs ->
el "u" $ renderInlines cfg xs
Strikeout xs ->
el "strike" $ renderInlines cfg xs
Superscript xs ->
el "sup" $ renderInlines cfg xs
Subscript xs ->
el "sub" $ renderInlines cfg xs
SmallCaps xs ->
el "small" $ renderInlines cfg xs
Quoted qt xs ->
flip inQuotes qt $ renderInlines cfg xs
Cite _ _ -> do
el "pre" $ text "error[reflex-doc-pandoc]: Pandoc Cite is not handled"
pure mempty
Code attr x ->
elPandocAttr "code" attr $ do
text x
pure mempty
Space ->
text " " >> pure mempty
SoftBreak ->
text " " >> pure mempty
LineBreak ->
el "br" blank >> pure mempty
RawInline fmt x ->
elPandocRaw fmt x >> pure mempty
Math mathType s -> do
case mathType of
InlineMath ->
elClass "span" "math inline" $ text $ "\\(" <> s <> "\\)"
DisplayMath ->
elClass "span" "math display" $ text "$$" >> text s >> text "$$"
pure mempty
inline@(Link attr xs (lUrl, lTitle)) -> do
let defaultRender = do
let attr' = sansEmptyAttrs $ renderAttr attr <> ("href" =: lUrl <> "title" =: lTitle)
elAttr "a" attr' $ renderInlines cfg xs
case uriLinkFromInline inline of
Just uriLink -> do
fns <- ask
lift $ _config_renderURILink cfg (flip runReaderT fns defaultRender) uriLink
Nothing ->
defaultRender
Image attr xs (iUrl, iTitle) -> do
let attr' = sansEmptyAttrs $ renderAttr attr <> ("src" =: iUrl <> "title" =: iTitle)
elAttr "img" attr' $ renderInlines cfg xs
Note xs -> do
fs :: Footnotes <- ask
case Map.lookup (mkFootnote xs) fs of
Nothing ->
elClass "aside" "footnote-inline" $ renderBlocks cfg xs
Just idx ->
renderFootnoteRef idx >> pure mempty
Span attr xs ->
elPandocAttr "span" attr $
renderInlines cfg xs
where
inQuotes w = \case
SingleQuote -> text "‘" >> w <* text "’"
DoubleQuote -> text "“" >> w <* text "”"