atomic-css-0.2.0: src/Web/Atomic/Html.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedLists #-}
module Web.Atomic.Html where
import Data.List qualified as L
import Data.Map.Strict (Map)
import Data.String (IsString (..))
import Data.Text (Text, pack)
import GHC.Exts (IsList (..))
import Web.Atomic.Types
{- | Html monad
@
import Web.Atomic
example = do
'el' ~ pad 10 $ do
'el' ~ fontSize 24 . bold $ "My Links"
a '@' href "hoogle.haskell.org" ~ link $ \"Hoogle\"
a '@' href "hackage.haskell.org" ~ link $ \"Hackage\"
link = underline . color Primary
a = 'tag' "a"
href = 'att' "href"
@
-}
data Html a = Html {value :: a, nodes :: [Node]}
instance IsList (Html ()) where
type Item (Html ()) = Node
fromList = Html () . fromList
toList (Html _ ns) = ns
instance IsString (Html ()) where
fromString s = Html () [fromString s]
instance Functor Html where
fmap f (Html a ns) = Html (f a) ns
instance Applicative Html where
pure a = Html a []
(<*>) :: Html (a -> b) -> Html a -> Html b
Html f nfs <*> Html a nas =
Html (f a) (nfs <> nas)
-- ha *> hb = ha <> hb
instance Monad Html where
(>>=) :: forall a b. Html a -> (a -> Html b) -> Html b
Html a nas >>= famb =
let Html b nbs = famb a :: Html b
in Html b (nas <> nbs)
el :: Html () -> Html ()
el = tag "div"
tag :: Text -> Html () -> Html ()
tag nm (Html _ content) = do
fromElement $ (element nm){content}
tag' :: Bool -> Text -> Html () -> Html ()
tag' inline nm (Html _ content) =
fromElement $ (element nm){content, inline}
fromElement :: Element -> Html ()
fromElement elm =
Html () [Elem elm]
text :: Text -> Html ()
text t = Html () [Text t]
none :: Html ()
none = pure ()
raw :: Text -> Html ()
raw t = Html () [Raw t]
-- | A single 'Html' element. Note that the class attribute is generated separately from the css rules, rather than the attributes
data Element = Element
{ inline :: Bool
, name :: Text
, css :: [Rule]
, attributes :: Map Name AttValue
, content :: [Node]
}
data Node
= Elem Element
| Text Text
| Raw Text
instance IsString Node where
fromString s = Text (pack s)
mapElement :: (Element -> Element) -> Html a -> Html a
mapElement f (Html a ns) = Html a $ fmap (mapNodeElement f) ns
mapNodeElement :: (Element -> Element) -> Node -> Node
mapNodeElement f (Elem e) = Elem $ f e
mapNodeElement _ n = n
element :: Text -> Element
element nm = Element False nm mempty mempty mempty
instance Attributable (Html a) where
modAttributes f =
mapElement (\elm -> elm{attributes = f elm.attributes})
instance Styleable (Html a) where
modCSS f =
mapElement (\elm -> elm{css = f elm.css})
htmlCSSRules :: Html a -> Map Selector Rule
htmlCSSRules (Html _ ns) = mconcat $ fmap nodeCSSRules ns
nodeCSSRules :: Node -> Map Selector Rule
nodeCSSRules = \case
Elem elm -> elementCSSRules elm
_ -> []
elementCSSRules :: Element -> Map Selector Rule
elementCSSRules elm =
ruleMap elm.css <> mconcat (fmap nodeCSSRules elm.content)
elementClasses :: Element -> [ClassName]
elementClasses elm =
L.sort $ fmap ruleClassName elm.css