module Config.Helpers.Markdown exposing (..) import Browser import Config.Helpers.Format exposing ( headerFontSizeBig , headerFontSizeMedium , headerFontSizeSmall , paragraphFontSize ) import Config.Helpers.Response exposing (pageList) import Config.Style.Colour exposing (colourTheme) import Config.Style.Transitions exposing ( hoverFontDarkOrange , hoverFontLightOrange , transitionStyleFast , transitionStyleMedium ) import Element as E exposing (..) import Element.Background as B import Element.Border as D import Element.Font as F import Element.Input as Input import Element.Region as Region import Html exposing (Attribute, Html) import Html.Attributes import Markdown.Block as Block exposing (Block, Inline, ListItem(..), Task(..)) import Markdown.Html import Markdown.Parser import Markdown.Renderer articleImage : String -> Element msg articleImage pic = el [ centerX , width fill ] <| column [ E.paddingEach { top = 20 , bottom = 20 , left = 20 , right = 20 } ] [ image [ width fill ] { src = "/blog/" ++ pic ++ ".png", description = "" } ] renderDeviceMarkdown : String -> Element msg renderDeviceMarkdown markdown = case renderMarkdown markdown of Ok renderedMarkdown -> column [ width fill , centerX , spacing 10 ] renderedMarkdown Err error -> E.text error renderMarkdown : String -> Result String (List (Element msg)) renderMarkdown markdown = markdown |> Markdown.Parser.parse |> Result.mapError (\error -> error |> List.map Markdown.Parser.deadEndToString |> String.join "\n") |> Result.andThen (Markdown.Renderer.render elmUiRenderer) elmUiRenderer : Markdown.Renderer.Renderer (Element msg) elmUiRenderer = { heading = heading , paragraph = E.paragraph [ E.spacing 3 , paragraphFontSize , F.alignLeft , width fill ] , thematicBreak = E.none , text = E.text , strong = \content -> E.row [ F.bold ] content , emphasis = \content -> E.row [ F.italic ] content , strikethrough = \content -> E.row [ F.strike ] content , codeSpan = code , link = \{ title, destination } body -> E.newTabLink [ width fill ] { url = destination , label = E.paragraph [ F.color colourTheme.textLightOrange , paragraphFontSize , transitionStyleFast , hoverFontDarkOrange ] body } , hardLineBreak = Html.br [] [] |> E.html , image = \image -> case image.title of Just title -> el [ width fill ] <| column [ E.centerX , E.centerY , width fill , E.paddingEach { top = 0 , bottom = 10 , left = 10 , right = 10 } ] [ E.image [ E.centerX , width (fill |> maximum 600) ] { src = image.src , description = image.alt } ] Nothing -> el [ width fill ] <| column [ E.centerX , E.centerY , width fill , E.paddingEach { top = 3 , bottom = 10 , left = 10 , right = 10 } ] [ E.image [ E.centerX , width (fill |> maximum 600) ] { src = image.src , description = image.alt } ] , blockQuote = \children -> E.column [ D.widthEach { top = 0 , right = 0 , bottom = 0 , left = 5 } , D.rounded 10 , E.paddingEach { top = 13 , bottom = 10 , left = 10 , right = 10 } , D.color colourTheme.textLightOrange , B.color colourTheme.backgroundLightGrey , paragraphFontSize , width fill ] children , unorderedList = \items -> E.column [ E.spacing 3 , paragraphFontSize ] (items |> List.map (\(ListItem task children) -> E.row [ E.width fill ] [ E.el [ E.width <| E.px 20 , E.alignTop , alignRight ] (case task of IncompleteTask -> Input.defaultCheckbox False CompletedTask -> Input.defaultCheckbox True NoTask -> E.text "•" ) , E.column [ E.width fill, F.alignLeft ] children ] ) ) , orderedList = \startingIndex items -> E.column [ E.spacing 3 , paragraphFontSize , E.width fill ] (items |> List.indexedMap (\index itemBlocks -> E.row [ E.width fill ] [ E.el [ E.alignTop , width <| px 25 , F.bold , alignRight ] (E.text (String.fromInt (index + startingIndex) ++ ".")) , E.column [ alignLeft , E.width fill ] itemBlocks ] ) ) , codeBlock = codeBlock , html = Markdown.Html.oneOf [] , table = E.column [ width fill ] , tableHeader = E.column [ width fill ] , tableBody = E.column [ width fill ] , tableRow = E.row [ width fill ] , tableHeaderCell = \maybeAlignment children -> E.paragraph [ width fill ] children , tableCell = \maybeAlignment children -> E.paragraph [ width fill ] children } code : String -> Element msg code snippet = E.el [ B.color colourTheme.backgroundLightGrey , D.rounded 2 , E.paddingXY 5 3 , width fill , F.family [ F.external { url = "https://fonts.googleapis.com/css?family=Source+Code+Pro" , name = "Source Code Pro" } ] ] (E.text snippet) codeBlock : { body : String, language : Maybe String } -> Element msg codeBlock details = E.el [ B.color colourTheme.backgroundLightGrey , E.htmlAttribute (Html.Attributes.style "white-space" "pre") , width fill , E.paddingEach { top = 23 , bottom = 20 , left = 20 , right = 20 } , paragraphFontSize , F.family [ F.external { url = "https://fonts.googleapis.com/css?family=Source+Code+Pro" , name = "Source Code Pro" } ] ] (E.text details.body) heading : { level : Block.HeadingLevel, rawText : String, children : List (Element msg) } -> Element msg heading { level, rawText, children } = E.paragraph [ F.size (case level of Block.H1 -> headerFontSizeBig Block.H2 -> headerFontSizeMedium _ -> headerFontSizeSmall ) , F.bold , F.center , width fill , F.color colourTheme.textLightOrange , Region.heading (Block.headingLevelToInt level) , E.htmlAttribute (Html.Attributes.attribute "name" (rawTextToId rawText)) , E.htmlAttribute (Html.Attributes.id (rawTextToId rawText)) ] children rawTextToId rawText = rawText |> String.split " " |> Debug.log "split" |> String.join "-" |> Debug.log "joined" |> String.toLower