packages feed

mjml-0.1.0.0: src/Text/Mjml/Component/Social.hs

-- | @mj-social@ and @mj-social-element@.
module Text.Mjml.Component.Social (
    mjSocial,
    mjSocialElement,
) where

import Data.Maybe (fromMaybe)
import Data.Text (Text)
import Data.Text qualified as T

import Text.Mjml.Js (parseIntJs, showNumber)
import Text.Mjml.Render


mjSocial :: Component
mjSocial =
    (defComponent "mj-social" build')
        { allowedAttributes =
            [ ("align", "enum(left,right,center)")
            , ("border", "string")
            , ("border-radius", "string")
            , ("container-background-color", "color")
            , ("color", "color")
            , ("font-family", "string")
            , ("font-size", "unit(px)")
            , ("font-style", "string")
            , ("font-weight", "string")
            , ("icon-size", "unit(px,%)")
            , ("icon-height", "unit(px,%)")
            , ("icon-padding", "unit(px,%){1,4}")
            , ("inner-padding", "unit(px,%){1,4}")
            , ("line-height", "unit(px,%,)")
            , ("mode", "enum(horizontal,vertical)")
            , ("padding-bottom", "unit(px,%)")
            , ("padding-left", "unit(px,%)")
            , ("padding-right", "unit(px,%)")
            , ("padding-top", "unit(px,%)")
            , ("padding", "unit(px,%){1,4}")
            , ("table-layout", "enum(auto,fixed)")
            , ("text-padding", "unit(px,%){1,4}")
            , ("text-decoration", "string")
            , ("vertical-align", "enum(top,bottom,middle)")
            ]
        , defaults =
            [ ("align", "center")
            , ("border-radius", "3px")
            , ("color", "#333333")
            , ("font-family", "Ubuntu, Helvetica, Arial, sans-serif")
            , ("font-size", "13px")
            , ("icon-size", "20px")
            , ("line-height", "22px")
            , ("mode", "horizontal")
            , ("padding", "10px 25px")
            , ("text-decoration", "none")
            ]
        }
  where
    build' s = inst
      where
        inst =
            (defInstance s render')
                { getStyles = \case
                    "tableVertical" -> [style "margin" "0px"]
                    _ -> []
                }

        render' =
            if attr s "mode" == Just "horizontal" then renderHorizontal else renderVertical

        socialElementAttributes =
            [("padding", p) | Just p <- [attr s "inner-padding"], truthy (Just p)]
                `merge` [ (k, v)
                        | k <-
                            [ "border"
                            , "border-radius"
                            , "color"
                            , "font-family"
                            , "font-size"
                            , "font-weight"
                            , "font-style"
                            , "icon-size"
                            , "icon-height"
                            , "icon-padding"
                            , "text-padding"
                            , "line-height"
                            , "text-decoration"
                            ]
                        , Just v <- [attr s k]
                        ]

        renderHorizontal = do
            kids <-
                renderChildren
                    inst
                    defChildOptions
                        { childAttributes = socialElementAttributes
                        , renderer = \c ->
                            if rawElement (component (self c))
                                then render c
                                else do
                                    body <- render c
                                    pure $
                                        mconcat
                                            [ startConditionalTag
                                            , "<td>"
                                            , endConditionalTag
                                            , "<table"
                                            , htmlAttributes
                                                c
                                                [ ("align", maybe ANone AV (attr s "align"))
                                                , ("border", AV "0")
                                                , ("cellpadding", AV "0")
                                                , ("cellspacing", AV "0")
                                                , ("role", AV "presentation")
                                                , ("style", AStyles [style "float" "none", style "display" "inline-table"])
                                                ]
                                            , "><tbody>"
                                            , body
                                            , "</tbody></table>"
                                            , startConditionalTag
                                            , "</td>"
                                            , endConditionalTag
                                            ]
                        }
            pure $
                mconcat
                    [ startConditionalTag
                    , "<table"
                    , htmlAttributes
                        inst
                        [ ("align", maybe ANone AV (attr s "align"))
                        , ("border", AV "0")
                        , ("cellpadding", AV "0")
                        , ("cellspacing", AV "0")
                        , ("role", AV "presentation")
                        ]
                    , "><tr>"
                    , endConditionalTag
                    , kids
                    , startConditionalTag
                    , "</tr></table>"
                    , endConditionalTag
                    ]

        renderVertical = do
            kids <- renderChildren inst defChildOptions {childAttributes = socialElementAttributes}
            pure $
                mconcat
                    [ "<table"
                    , htmlAttributes
                        inst
                        [ ("border", AV "0")
                        , ("cellpadding", AV "0")
                        , ("cellspacing", AV "0")
                        , ("role", AV "presentation")
                        , ("style", AStyleOf "tableVertical")
                        ]
                    , "><tbody>"
                    , kids
                    , "</tbody></table>"
                    ]


imgBaseUrl :: Text
imgBaseUrl = "https://www.mailjet.com/images/theme/v1/icons/ico-social/"


-- | The networks MJML knows by name, each also available as @NAME-noshare@,
-- which links to the URL as given instead of through a share page.
socialNetworks :: [(Text, Attrs)]
socialNetworks = base <> [(k <> "-noshare", merge v [("share-url", "[[URL]]")]) | (k, v) <- base]
  where
    base =
        [ ("facebook", [("share-url", "https://www.facebook.com/sharer/sharer.php?u=[[URL]]"), ("background-color", "#3b5998"), ("src", imgBaseUrl <> "facebook.png")])
        , ("twitter", [("share-url", "https://twitter.com/intent/tweet?url=[[URL]]"), ("background-color", "#55acee"), ("src", imgBaseUrl <> "twitter.png")])
        , ("x", [("share-url", "https://twitter.com/intent/tweet?url=[[URL]]"), ("background-color", "#000000"), ("src", imgBaseUrl <> "twitter-x.png")])
        , ("google", [("share-url", "https://plus.google.com/share?url=[[URL]]"), ("background-color", "#dc4e41"), ("src", imgBaseUrl <> "google-plus.png")])
        , ("pinterest", [("share-url", "https://pinterest.com/pin/create/button/?url=[[URL]]&media=&description="), ("background-color", "#bd081c"), ("src", imgBaseUrl <> "pinterest.png")])
        , ("linkedin", [("share-url", "https://www.linkedin.com/shareArticle?mini=true&url=[[URL]]&title=&summary=&source="), ("background-color", "#0077b5"), ("src", imgBaseUrl <> "linkedin.png")])
        , ("instagram", [("background-color", "#3f729b"), ("src", imgBaseUrl <> "instagram.png")])
        , ("web", [("src", imgBaseUrl <> "web.png"), ("background-color", "#4BADE9")])
        , ("snapchat", [("src", imgBaseUrl <> "snapchat.png"), ("background-color", "#FFFA54")])
        , ("youtube", [("src", imgBaseUrl <> "youtube.png"), ("background-color", "#EB3323")])
        , ("tumblr", [("src", imgBaseUrl <> "tumblr.png"), ("share-url", "https://www.tumblr.com/widgets/share/tool?canonicalUrl=[[URL]]"), ("background-color", "#344356")])
        , ("github", [("src", imgBaseUrl <> "github.png"), ("background-color", "#000000")])
        , ("xing", [("src", imgBaseUrl <> "xing.png"), ("share-url", "https://www.xing.com/app/user?op=share&url=[[URL]]"), ("background-color", "#296366")])
        , ("vimeo", [("src", imgBaseUrl <> "vimeo.png"), ("background-color", "#53B4E7")])
        , ("medium", [("src", imgBaseUrl <> "medium.png"), ("background-color", "#000000")])
        , ("soundcloud", [("src", imgBaseUrl <> "soundcloud.png"), ("background-color", "#EF7F31")])
        , ("dribbble", [("src", imgBaseUrl <> "dribbble.png"), ("background-color", "#D95988")])
        ]


mjSocialElement :: Component
mjSocialElement =
    (defComponent "mj-social-element" build')
        { endingTag = True
        , allowedAttributes =
            [ ("align", "enum(left,center,right)")
            , ("icon-position", "enum(left,right)")
            , ("background-color", "color")
            , ("color", "color")
            , ("border", "string")
            , ("border-radius", "string")
            , ("font-family", "string")
            , ("font-size", "unit(px)")
            , ("font-style", "string")
            , ("font-weight", "string")
            , ("href", "string")
            , ("icon-size", "unit(px,%)")
            , ("icon-height", "unit(px,%)")
            , ("icon-padding", "unit(px,%){1,4}")
            , ("line-height", "unit(px,%,)")
            , ("name", "string")
            , ("padding-bottom", "unit(px,%)")
            , ("padding-left", "unit(px,%)")
            , ("padding-right", "unit(px,%)")
            , ("padding-top", "unit(px,%)")
            , ("padding", "unit(px,%){1,4}")
            , ("text-padding", "unit(px,%){1,4}")
            , ("rel", "string")
            , ("src", "string")
            , ("srcset", "string")
            , ("sizes", "string")
            , ("alt", "string")
            , ("title", "string")
            , ("target", "string")
            , ("text-decoration", "string")
            , ("vertical-align", "enum(top,middle,bottom)")
            ]
        , defaults =
            [ ("alt", "")
            , ("align", "left")
            , ("icon-position", "left")
            , ("color", "#000")
            , ("border", "0")
            , ("border-radius", "3px")
            , ("font-family", "Ubuntu, Helvetica, Arial, sans-serif")
            , ("font-size", "13px")
            , ("line-height", "1")
            , ("padding", "4px")
            , ("text-padding", "4px 4px 4px 0")
            , ("target", "_blank")
            , ("text-decoration", "none")
            , ("vertical-align", "middle")
            ]
        }
  where
    build' s = inst
      where
        inst = (defInstance s (pure rendered)) {getStyles = stylesFor}

        network = fromMaybe [] (attr s "name" >>= \n -> lookup n socialNetworks)

        -- getSocialAttributes
        href = case attr s "href" of
            Just h
                | truthy (Just h)
                , Just shareUrl <- lookup "share-url" network
                , truthy (Just shareUrl) ->
                    let (before, after) = T.breakOn "[[URL]]" shareUrl
                     in Just (if T.null after then shareUrl else before <> h <> T.drop 7 after)
            other -> other
        social k = case attr s k of
            Just v | truthy (Just v) -> Just v
            _ -> lookup k network
        iconSize = social "icon-size"
        iconHeight = social "icon-height"
        backgroundColor = social "background-color"
        src = social "src"
        srcset = social "srcset"
        sizes = social "sizes"

        stylesFor = \case
            "td" ->
                [ ("padding", attr s "padding")
                , ("padding-top", attr s "padding-top")
                , ("padding-right", attr s "padding-right")
                , ("padding-bottom", attr s "padding-bottom")
                , ("padding-left", attr s "padding-left")
                , ("vertical-align", attr s "vertical-align")
                ]
            "table" ->
                [ ("background", backgroundColor)
                , ("border-radius", attr s "border-radius")
                , ("width", iconSize)
                ]
            "icon" ->
                [ ("padding", attr s "icon-padding")
                , style "font-size" "0"
                , ("height", if truthy iconHeight then iconHeight else iconSize)
                , style "vertical-align" "middle"
                , ("width", iconSize)
                ]
            "img" ->
                [ ("border", attr s "border")
                , ("border-radius", attr s "border-radius")
                , style "display" "block"
                ]
            "tdText" ->
                [ style "vertical-align" "middle"
                , ("padding", attr s "text-padding")
                , ("text-align", attr s "align")
                ]
            "text" ->
                [ ("color", attr s "color")
                , ("font-size", attr s "font-size")
                , ("font-weight", attr s "font-weight")
                , ("font-style", attr s "font-style")
                , ("font-family", attr s "font-family")
                , ("line-height", attr s "line-height")
                , ("text-decoration", attr s "text-decoration")
                ]
            _ -> []

        hasLink = truthy (attr s "href")
        opt = maybe ANone AV

        linkAttrs extra =
            [("href", opt href)]
                <> extra
                <> [("rel", opt (attr s "rel")), ("target", opt (attr s "target"))]

        makeIcon =
            mconcat
                [ "<td"
                , htmlAttributes inst [("style", AStyleOf "td")]
                , "><table"
                , htmlAttributes
                    inst
                    [ ("border", AV "0")
                    , ("cellpadding", AV "0")
                    , ("cellspacing", AV "0")
                    , ("role", AV "presentation")
                    , ("style", AStyleOf "table")
                    ]
                , "><tbody><tr><td"
                , htmlAttributes inst [("style", AStyleOf "icon")]
                , ">"
                , if hasLink then "<a" <> htmlAttributes inst (linkAttrs []) <> ">" else ""
                , "<img"
                , htmlAttributes
                    inst
                    [ ("alt", opt (attr s "alt"))
                    , ("title", opt (attr s "title"))
                    , ("src", opt src)
                    , ("style", AStyleOf "img")
                    , ("width", AV (showNumber (parseIntJs (fromMaybe "" iconSize))))
                    , ("sizes", opt sizes)
                    , ("srcset", opt srcset)
                    ]
                , " />"
                , if hasLink then "</a>" else ""
                , "</td></tr></tbody></table></td>"
                ]

        makeContent
            | T.null (getContent s) = ""
            | otherwise =
                mconcat
                    [ "<td"
                    , htmlAttributes inst [("style", AStyleOf "tdText")]
                    , ">"
                    , if hasLink
                        then "<a" <> htmlAttributes inst (linkAttrs [("style", AStyleOf "text")]) <> ">"
                        else "<span" <> htmlAttributes inst [("style", AStyleOf "text")] <> ">"
                    , fromText (getContent s)
                    , if hasLink then "</a>" else "</span>"
                    , "</td>"
                    ]

        rendered =
            mconcat
                [ "<tr"
                , htmlAttributes inst [("class", opt (attr s "css-class"))]
                , ">"
                , if attr s "icon-position" == Just "left"
                    then makeIcon <> " " <> makeContent
                    else makeContent <> " " <> makeIcon
                , "</tr>"
                ]