{- Copied from https://github.com/matheus23/elm-markdown-transforms/blob/02fddd3bf9e82412eb289bead3b5124a98163ab6/src/Markdown/Scaffolded.elm Modified to use DataSource instead of StaticHttp -} module Markdown.Scaffolded exposing ( Block(..) , map, indexedMap , parameterized, validating, withDataSource , reduceHtml, reduceWords, reducePretty, reduce , foldFunction, foldResults, foldStaticHttpRequests, foldIndexed , fromRenderer, toRenderer , bumpHeadings ) {-| # Rendering Markdown with Scaffolds, Reducers and Folds (This is called recursion-schemes in other languages, but don't worry, you don't have to write recursive functions (this is the point of all of this ;) )!) This is module provides a more **complicated**, but also **more powerful and composable** way of rendering markdown than the built-in elm-markdown [`Renderer`](https://package.elm-lang.org/packages/dillonkearns/elm-markdown/latest/Markdown-Renderer). If you feel a little overwhelmed with this module at first, I recommend taking a look at the [What are reducers?](#what-are-reducers-) section. # Main Datastructure @docs Block @docs map, indexedMap # High-level Transformations These functions are not as composable as [transformation building blocks](#transformation-building-blocks), but might suffice for your use case. Take a look at the other section if you find you need something better. @docs parameterized, validating, withDataSource # Transformation Building Blocks @docs reduceHtml, reduceWords, reducePretty, reduce @docs foldFunction, foldResults, foldStaticHttpRequests, foldIndexed ### What are 'reducers'? In this context of the library, we're often working with functions of the type `Block view -> view`, where `view` might be something like `Html Msg` or `String`, etc. or, generally, functions of structure `Block a -> b`. I refer to functions of that structure as 'reducers'. (This is somewhat different to the 'real' terminology, but I feel like they capture the nature of 'reducing once' very well.) If you know `List.foldr` you already know an example for a reducer (the first argument)! The reducers in this module are no different, we just write them in different ways. We can do the same thing we did for this library for lists: type ListScaffold elem a = Empty | Cons elem a reduceEmpty = 0 reduceCons a b = a + b handler listElement = case listElement of Empty -> reduceEmpty Cons elem accumulated -> reduceCons elem accumulated foldl : (ListScaffold a b -> b) -> List a -> b foldl handle list = case list of [] -> handle Empty (x:xs) -> handle (Cons x xs) foldl handler == List.foldl reduceCons reduceEmpty The last line illustrates how different ways of writing these reducers relate: For `List.foldl` we simply provide the cases (empty or cons) as different arguments, for reducers in this library, we create a custom type case for empty and cons. ### What are 'folds'? Some functions have similar, but not quite the type that a reducers has. For example: - `Block (Request a) -> Request (Block a)` - `Block (Maybe a) -> Maybe (Block a)` - `Block (Result err a) -> Result err (Block a)` - `Block (environment -> a) -> environment -> Block a` All of these examples have the structure `Block (F a) -> F (Block a)` for some `F`. You might have to squint your eyes at the last two of these examples. Especially the last one. Let me rewrite it with a type alias: type alias Function a b = a -> b foldFunction : Block (Function env a) -> Function env (Block a) ### Combining Reducers You can combine multiple 'reducers' into one. There's no function for doing this, but a pattern you might want to follow. Let's say you want to accumulate both all the words in your markdown and the `Html` you want it to render to, then you can do this: type alias Rendered = { html : Html Msg , words : List String } reduceRendered : Block Rendered -> Rendered reduceRendered block = { html = block |> map .html |> reduceHtml , words = block |> map .words |> reduceWords } If you want to render to more things, just add another parameter to the record type and follow the pattern. It is even possible to let the rendered html to depend on the words inside itself (or maybe something else you're additionally reducing to). # Conversions Did you already start to write a custom elm-markdown `Renderer`, but want to use this library? Don't worry. They're compatible. You can convert between them! @docs fromRenderer, toRenderer # Utilities I mean to aggregate utilites for transforming Blocks in this section. @docs bumpHeadings -} import DataSource exposing (DataSource) import Html exposing (Html) import Html.Attributes as Attr import Markdown.Block as Block import Markdown.Html import Markdown.Renderer exposing (Renderer) import Regex import Result.Extra as Result -- EXPOSED DEFINITIONS {-| A datatype that enumerates all possible ways markdown could wrap some children. Kind of like a 'Scaffold' around something that's already built, which will get torn down after building is finished. This does not include Html tags. If you look at the left hand sides of all of the functions in the elm-markdown [`Renderer`](https://package.elm-lang.org/packages/dillonkearns/elm-markdown/latest/Markdown-Renderer), you'll notice a similarity to this custom type, except it's missing a type for 'html'. Defining this data structure has some advantages in composing multiple Renderers. It has a type parameter `children`, which is supposed to be filled with `String`, `Html msg` or similar. Take a look at some [reducers](#transformation-building-blocks) for examples of this. There are some neat tricks you can do with this data structure, for example, `Block Never` represents only non-nested blocks of markdown. -} type Block children = Heading { level : Block.HeadingLevel, rawText : String, children : List children } | Paragraph (List children) | BlockQuote (List children) | Text String | CodeSpan String | Strong (List children) | Emphasis (List children) | Strikethrough (List children) | Link { title : Maybe String, destination : String, children : List children } | Image { alt : String, src : String, title : Maybe String } | UnorderedList { items : List (Block.ListItem children) } | OrderedList { startingIndex : Int, items : List (List children) } | CodeBlock { body : String, language : Maybe String } | HardLineBreak | ThematicBreak | Table (List children) | TableHeader (List children) | TableBody (List children) | TableRow (List children) | TableCell (Maybe Block.Alignment) (List children) | TableHeaderCell (Maybe Block.Alignment) (List children) {-| Transform each child of a `Block` using the given function. For example, we can transform the lists of words inside each block into concatenated Strings: wordsToWordlist : Block (List String) -> Block String wordsToWordlist block = map (\listOfWords -> String.join ", " listOfWords) block Paragraph [ [ "This", "paragraph", "was", "full", "of", "individual", "words", "once." ] , [ "It", "also", "contained", "another", "paragraph" ] ] |> wordsToWordlist --> Paragraph --> [ "This, paragraph, was, full, of, individual, words, once." --> , "It, also, contained, another, paragraph" --> ] HardLineBreak |> wordsToWordlist --> HardLineBreak The ability to define this function is one of the reasons for our `Block` definition. If you try defining `map` for elm-markdown's `Renderer` you'll find out it doesn't work. -} map : (a -> b) -> Block a -> Block b map f markdown = case markdown of Heading { level, rawText, children } -> Heading { level = level, rawText = rawText, children = List.map f children } Paragraph children -> Paragraph (List.map f children) BlockQuote children -> BlockQuote (List.map f children) Text content -> Text content CodeSpan content -> CodeSpan content Strong children -> Strong (List.map f children) Emphasis children -> Emphasis (List.map f children) Strikethrough children -> Strikethrough (List.map f children) Link { title, destination, children } -> Link { title = title, destination = destination, children = List.map f children } Image imageInfo -> Image imageInfo UnorderedList { items } -> UnorderedList { items = List.map (\(Block.ListItem task children) -> Block.ListItem task (List.map f children) ) items } OrderedList { startingIndex, items } -> OrderedList { startingIndex = startingIndex, items = List.map (List.map f) items } CodeBlock codeBlockInfo -> CodeBlock codeBlockInfo HardLineBreak -> HardLineBreak ThematicBreak -> ThematicBreak Table children -> Table (List.map f children) TableHeader children -> TableHeader (List.map f children) TableBody children -> TableBody (List.map f children) TableRow children -> TableRow (List.map f children) TableCell alignment children -> TableCell alignment (List.map f children) TableHeaderCell alignment children -> TableHeaderCell alignment (List.map f children) {-| Block's children are mapped from 0 to n (if n+1 is the amount of children). Most arguments to the mapping function are therefore [0], [1], ... etc. All children will get unique `List Int` arguments. In some cases like lists, there might be two levels of indices: [0,0], or [1,0]. In these cases, the first integer is the 'closest' index from the point of view of the child. OrderedList { startingIndex = 0 , items = [ [ (), () ] , [ (), (), () ] ] } |> indexedMap (\indices _ -> indices) --> OrderedList --> { startingIndex = 0 --> , items = --> [ [ [ 0, 0 ], [ 1, 0 ] ] --> , [ [ 0, 1 ], [ 1, 1 ], [ 2, 1 ] ] --> ] --> } -} indexedMap : (List Int -> a -> b) -> Block a -> Block b indexedMap f markdown = case markdown of Heading { level, rawText, children } -> Heading { level = level, rawText = rawText, children = List.indexedMap (\index -> f [ index ]) children } Paragraph children -> Paragraph (List.indexedMap (\index -> f [ index ]) children) BlockQuote children -> BlockQuote (List.indexedMap (\index -> f [ index ]) children) Text content -> Text content CodeSpan content -> CodeSpan content Strong children -> Strong (List.indexedMap (\index -> f [ index ]) children) Emphasis children -> Emphasis (List.indexedMap (\index -> f [ index ]) children) Strikethrough children -> Strikethrough (List.indexedMap (\index -> f [ index ]) children) Link { title, destination, children } -> Link { title = title, destination = destination, children = List.indexedMap (\index -> f [ index ]) children } Image imageInfo -> Image imageInfo UnorderedList { items } -> UnorderedList { items = List.indexedMap (\indexA (Block.ListItem task children) -> Block.ListItem task (List.indexedMap (\indexB -> f [ indexB, indexA ]) children) ) items } OrderedList { startingIndex, items } -> OrderedList { startingIndex = startingIndex, items = List.indexedMap (\indexA -> List.indexedMap (\indexB -> f [ indexB, indexA ])) items } CodeBlock codeBlockInfo -> CodeBlock codeBlockInfo HardLineBreak -> HardLineBreak ThematicBreak -> ThematicBreak Table children -> Table (List.indexedMap (\index -> f [ index ]) children) TableHeader children -> TableHeader (List.indexedMap (\index -> f [ index ]) children) TableBody children -> TableBody (List.indexedMap (\index -> f [ index ]) children) TableRow children -> TableRow (List.indexedMap (\index -> f [ index ]) children) TableCell alignment children -> TableCell alignment (List.indexedMap (\index -> f [ index ]) children) TableHeaderCell alignment children -> TableHeaderCell alignment (List.indexedMap (\index -> f [ index ]) children) {-| Use this function if you want to parameterize your view by an environment. Another way of thinking about this use-case is: use this if you want to 'render to functions'. Examples for what the `environment` type variable can be: - A `Model`, for rendering to `Model -> Html Msg` for `view`. - Templating information, in case you want to use markdown as templates and want to render to a function that expects templating parameters. Usually, for the above usecases you would have to define a function of type reduceTemplate : Block (TemplateInfo -> Html msg) -> (TemplateInfo -> Html msg) for example, so that you can turn it back into a `Renderer (Template Info -> Html msg)` for elm-markdown. If you were to define such a function, you would have to pass around the `TemplateInfo` parameter a lot. This function will take care of that for you. ### Anti use-cases In some cases using this function would be overkill. The alternative to this function is to simply parameterize your whole renderer (and not use this library): renderMarkdown : List String -> Block (Html Msg) -> Html Msg renderMarkdown censoredWords markdown = ... renderer : List String -> Markdown.Renderer (Html Msg) renderer censoredWords = toRenderer { renderHtml = ... , renderMarkdown = renderMarkdown censoredWords } In this example you can see how we pass through the 'censored words'. It behaves kind of like some global context in which we create our renderer. It is hard to convey the abstract notion of when to use `parameterized` and when not to. I'll give it a try: If you want to parse your markdown once and need to quickly render different versions of it (for example with different `Model`s or different `TemplateInfo`s), then use this. In other cases, if you probably only want to de-couple some variable out of your renderer that is pretty static in general (for example censored words), don't use this. ### `parameterized` over multiple Parameters If you want to parameterize your renderer over multiple variables, there are two options: 1. Add a field to the `environment` type used in this function 2. Take another parameter in curried form Although both are possible, I highly recommend the first option, as it is by far easier to deal with only one call to `parameterized`, not with two calls that would be required for option 2. ### Missing Functionality If this function doesn't quite do what you want, just try to re-create what you need by using `map` directly. `parameterized` basically just documents a pattern that is really easy to re-create: Its implementation is just 1 line of code. -} parameterized : (Block view -> environment -> view) -> (Block (environment -> view) -> (environment -> view)) parameterized reducer markdown env = reducer (map (\expectingEnv -> expectingEnv env) markdown) env {-| This transform enables validating the content of your `Block` before rendering. This function's most prominent usecases are linting markdown files, so for example: - Make sure all your code snippets are specified only with valid languages ('elm', 'javascript', 'js', 'html' etc.) - Make sure all your links are `https://` links - Generate errors/warnings on typos or words not contained in a dictionary - Disallow `h1` (alternatively, consider bumping the heading level) But it might also be possible that your `view` type can't _always_ be reduced from a `Block view` to a `view`, so you need to generate an error in these cases. ### Missing Functionality If this function doesn't quite do what you need to do, try using `foldResults`. The `validating` definition basically just documents a common pattern. Its implementation is just 1 line of code. -} validating : (Block view -> Result error view) -> (Block (Result error view) -> Result error view) validating reducer markdown = markdown |> foldResults |> Result.andThen reducer {-| This transform allows you to perform elm-pages' DataSource requests without having to think about how to thread these through your renderer. Some applications that can be realized like this: - Verifying that all links in your markdown do resolve at page build-time (Note: This currently needs some change in elm-pages, so it's not possible _yet_) - Giving custom elm-markdown HTML elements the ability to perform DataSource requests ### Missing Functionality If this function doesn't quite do what you need to do, try using `foldStaticHttpRequests`. The `wihtStaticHttpRequests` definition basically just documents a common pattern. Its implementation is just 1 line of code. -} withDataSource : (Block view -> DataSource view) -> (Block (DataSource view) -> DataSource view) withDataSource reducer markdown = markdown |> foldStaticHttpRequests |> DataSource.andThen reducer {-| This will reduce a `Block` to `Html` similar to what the [`defaultHtmlRenderer` in elm-markdown](https://package.elm-lang.org/packages/dillonkearns/elm-markdown/latest/Markdown-Renderer#defaultHtmlRenderer) does. That is, it renders similar to what the CommonMark spec expects. It also takes a list of attributes for convenience, so if you want to attach styles, id's, classes or events, you can use this. However, **the attributes parameter is ignored for `Text` nodes**. -} reduceHtml : List (Html.Attribute msg) -> Block (Html msg) -> Html msg reduceHtml attributes markdown = let attrsFromAlignment maybeAlignment = case maybeAlignment of Just Block.AlignLeft -> Attr.align "left" :: attributes Just Block.AlignCenter -> Attr.align "center" :: attributes Just Block.AlignRight -> Attr.align "right" :: attributes Nothing -> attributes in case markdown of Heading { level, children } -> case level of Block.H1 -> Html.h1 attributes children Block.H2 -> Html.h2 attributes children Block.H3 -> Html.h3 attributes children Block.H4 -> Html.h4 attributes children Block.H5 -> Html.h5 attributes children Block.H6 -> Html.h6 attributes children Paragraph children -> Html.p attributes children BlockQuote children -> Html.blockquote attributes children Text content -> Html.text content CodeSpan content -> Html.code attributes [ Html.text content ] Strong children -> Html.strong attributes children Emphasis children -> Html.em attributes children Strikethrough children -> Html.span (Attr.style "text-decoration" "line-through" :: attributes) children Link link -> case link.title of Just title -> Html.a (Attr.href link.destination :: Attr.title title :: attributes ) link.children Nothing -> Html.a (Attr.href link.destination :: attributes) link.children Image imageInfo -> case imageInfo.title of Just title -> Html.img (Attr.src imageInfo.src :: Attr.alt imageInfo.alt :: Attr.title title :: attributes ) [] Nothing -> Html.img (Attr.src imageInfo.src :: Attr.alt imageInfo.alt :: attributes ) [] UnorderedList { items } -> Html.ul attributes (items |> List.map (\item -> case item of Block.ListItem task children -> let checkbox = case task of Block.NoTask -> Html.text "" Block.IncompleteTask -> Html.input [ Attr.disabled True , Attr.checked False , Attr.type_ "checkbox" ] [] Block.CompletedTask -> Html.input [ Attr.disabled True , Attr.checked True , Attr.type_ "checkbox" ] [] in Html.li [] (checkbox :: children) ) ) OrderedList { startingIndex, items } -> Html.ol (case startingIndex of 1 -> Attr.start startingIndex :: attributes _ -> attributes ) (items |> List.map (\itemBlocks -> Html.li [] itemBlocks ) ) CodeBlock { body } -> Html.pre attributes [ Html.code [] [ Html.text body ] ] HardLineBreak -> Html.br attributes [] ThematicBreak -> Html.hr attributes [] Table children -> Html.table attributes children TableHeader children -> Html.thead attributes children TableBody children -> Html.tbody attributes children TableRow children -> Html.tr attributes children TableHeaderCell maybeAlignment children -> Html.th (attrsFromAlignment maybeAlignment) children TableCell maybeAlignment children -> Html.td (attrsFromAlignment maybeAlignment) children {-| Transform a block that contains functions into a function that produces blocks. One really common use-case is having access to a `Model` inside your html renderers. In these cases you want your markdown to be 'rendered to a function'. So let's say you've got a [`Markdown.Html.Renderer`]() like so: renderHtml : Markdown.Html.Renderer (List (Model -> Html Msg) -> (Model -> Html Msg) ) It has this type to be able to depend on the `Model`. Eventually you'll want to render to `Model -> Html Msg`. So now you can define your [`Markdown.Renderer.Renderer`]() like so: renderer : Markdown.Renderer.Renderer (Model -> Html Msg) renderer = toRenderer { renderHtml = renderHtml , renderMarkdown = renderMarkdown } renderMarkdown : Block (Model -> Html Msg) -> (Model -> Html Msg) renderMarkdown block model = foldFunction block -- ^ result : Model -> Block (Html Msg) model -- ^ result : Block (Html Msg) |> reduceHtml -- ^ result : Html Msg -} foldFunction : Block (environment -> view) -> (environment -> Block view) foldFunction markdown environment = markdown |> map ((|>) environment) {-| Fold your blocks with index information. This uses [`indexedMap`](#indexedMap) under the hood. This is quite advanced, but also very useful. If you're looking for a working example, please take a look at the test for this function. -} foldIndexed : Block (List Int -> view) -> (List Int -> Block view) foldIndexed markdown pathSoFar = markdown |> indexedMap (\indices view -> view (indices ++ pathSoFar)) {-| Extracts all words from the blocks and inlines. Excludes any markup characters, if they had an effect on the markup. The words are split according to the `\s` javascript regular expression (regex). Inline code spans are split, but **code blocks fragments are ignored** (code spans are included). If you need something more specific, I highly recommend rolling your own function for this. This is useful if you need to e.g. create header slugs. -} reduceWords : Block (List String) -> List String reduceWords = let whitespace = Regex.fromStringWith { caseInsensitive = True, multiline = True } "\\s" |> Maybe.withDefault Regex.never words = Regex.split whitespace extractWords block = case block of Text content -> words content CodeSpan content -> words content _ -> [] in reduce { extract = extractWords , accumulate = List.concat } {-| Thread results through your Blocks. The input is a block that contains possibly failed views. The output becomes `Err`, if any of the input block's children had an error (then it's the first error). If all of the block's children were `Ok`, then the result is going to be `Ok`. -} foldResults : Block (Result error view) -> Result error (Block view) foldResults markdown = case markdown of Heading { level, rawText, children } -> children |> Result.combine |> Result.map (\chdr -> Heading { level = level, rawText = rawText, children = chdr } ) Paragraph children -> children |> Result.combine |> Result.map Paragraph BlockQuote children -> children |> Result.combine |> Result.map BlockQuote Text content -> Text content |> Ok CodeSpan content -> CodeSpan content |> Ok Strong children -> children |> Result.combine |> Result.map Strong Emphasis children -> children |> Result.combine |> Result.map Emphasis Strikethrough children -> children |> Result.combine |> Result.map Strikethrough Link { title, destination, children } -> children |> Result.combine |> Result.map (\chdr -> Link { title = title, destination = destination, children = chdr } ) Image imageInfo -> Image imageInfo |> Ok UnorderedList { items } -> items |> List.map (\(Block.ListItem task children) -> children |> Result.combine |> Result.map (Block.ListItem task) ) |> Result.combine |> Result.map (\itms -> UnorderedList { items = itms }) OrderedList { startingIndex, items } -> items |> List.map Result.combine |> Result.combine |> Result.map (\itms -> OrderedList { startingIndex = startingIndex, items = itms } ) CodeBlock codeBlockInfo -> CodeBlock codeBlockInfo |> Ok HardLineBreak -> HardLineBreak |> Ok ThematicBreak -> ThematicBreak |> Ok Table children -> children |> Result.combine |> Result.map Table TableHeader children -> children |> Result.combine |> Result.map TableHeader TableBody children -> children |> Result.combine |> Result.map TableBody TableRow children -> children |> Result.combine |> Result.map TableRow TableHeaderCell maybeAlignment children -> children |> Result.combine |> Result.map (TableHeaderCell maybeAlignment) TableCell maybeAlignment children -> children |> Result.combine |> Result.map (TableCell maybeAlignment) {-| Accumulate elm-page's [`DataSource`](https://package.elm-lang.org/packages/dillonkearns/elm-pages/latest/Pages-DataSource#Request)s over blocks. Using this, it is possible to write reducers that produce views as a result of performing static http requests. -} foldStaticHttpRequests : Block (DataSource view) -> DataSource (Block view) foldStaticHttpRequests markdown = case markdown of Heading { level, rawText, children } -> children |> allStaticHttp |> DataSource.map (\chdr -> Heading { level = level, rawText = rawText, children = chdr } ) Paragraph children -> children |> allStaticHttp |> DataSource.map Paragraph BlockQuote children -> children |> allStaticHttp |> DataSource.map BlockQuote Text content -> Text content |> DataSource.succeed CodeSpan content -> CodeSpan content |> DataSource.succeed Strong children -> children |> allStaticHttp |> DataSource.map Strong Emphasis children -> children |> allStaticHttp |> DataSource.map Emphasis Strikethrough children -> children |> allStaticHttp |> DataSource.map Strikethrough Link { title, destination, children } -> children |> allStaticHttp |> DataSource.map (\chdr -> Link { title = title, destination = destination, children = chdr } ) Image imageInfo -> Image imageInfo |> DataSource.succeed UnorderedList { items } -> items |> List.map (\(Block.ListItem task children) -> children |> allStaticHttp |> DataSource.map (Block.ListItem task) ) |> allStaticHttp |> DataSource.map (\itms -> UnorderedList { items = itms }) OrderedList { startingIndex, items } -> items |> List.map allStaticHttp |> allStaticHttp |> DataSource.map (\itms -> OrderedList { startingIndex = startingIndex, items = itms } ) CodeBlock codeBlockInfo -> CodeBlock codeBlockInfo |> DataSource.succeed HardLineBreak -> HardLineBreak |> DataSource.succeed ThematicBreak -> ThematicBreak |> DataSource.succeed Table children -> children |> allStaticHttp |> DataSource.map Table TableHeader children -> children |> allStaticHttp |> DataSource.map TableHeader TableBody children -> children |> allStaticHttp |> DataSource.map TableBody TableRow children -> children |> allStaticHttp |> DataSource.map TableRow TableHeaderCell maybeAlignment children -> children |> allStaticHttp |> DataSource.map (TableHeaderCell maybeAlignment) TableCell maybeAlignment children -> children |> allStaticHttp |> DataSource.map (TableCell maybeAlignment) {-| Convert a block of markdown back to markdown text. (See the 'Formatting Markdown' test in the test suite.) This just renders one particular style of markdown. Your use-case might need something completely different. I recommend taking a look at the source code and adapting it to your needs. Note: **This function doesn't support GFM tables**. The function `Markdown.PrettyTables.reducePrettyTable` extends this function with table pretty-printing. Table pretty-printing is complicated, even when ignoring column sizes. The type `Block String -> String` is just "not powerful" enough to render a table to a string in such a way that it is syntactically valid again. -} reducePretty : Block String -> String reducePretty block = let escape toEscape = String.replace toEscape ("\\" ++ toEscape) in case block of Heading { level, children } -> (case level of Block.H1 -> "# " Block.H2 -> "## " Block.H3 -> "### " Block.H4 -> "#### " Block.H5 -> "##### " Block.H6 -> "###### " ) ++ String.concat children Text content -> content Paragraph children -> String.concat children BlockQuote children -> children |> String.concat |> String.split "\n" |> List.map (\line -> "> " ++ line) |> String.join "\n" Strong children -> -- TODO Escaping "**" ++ String.replace "**" "\\**" (String.concat children) ++ "**" Emphasis children -> "_" ++ escape "_" (String.concat children) ++ "_" Strikethrough children -> "~" ++ escape "~" (String.concat children) ++ "~" CodeSpan content -> "`" ++ content ++ "`" Link { destination, children } -> "[" ++ escape "]" (escape ")" (String.concat children)) ++ "](" ++ destination ++ ")" Image { alt, src, title } -> "![" ++ escape "]" (escape ")" alt) ++ "](" ++ src ++ (title |> Maybe.map (\t -> " \"" ++ escape "\"" t ++ "\"") |> Maybe.withDefault "" ) ++ ")" UnorderedList { items } -> items |> List.map (\(Block.ListItem task children) -> case task of Block.NoTask -> "- " ++ String.concat children Block.IncompleteTask -> "- [ ] " ++ String.concat children Block.CompletedTask -> "- [X] " ++ String.concat children ) |> String.join "\n" OrderedList { startingIndex, items } -> items |> List.indexedMap (\index children -> String.fromInt (index + startingIndex) ++ ". " ++ String.concat children ) |> String.join "\n" CodeBlock { body, language } -> case language of Just langName -> "```" ++ langName ++ "\n" ++ body ++ "```" Nothing -> let bodyLines = body |> String.split "\n" in if bodyLines |> List.any (not << String.startsWith " ") then bodyLines |> List.map ((++) " ") |> String.join "\n" else "```\n" ++ body ++ "```" HardLineBreak -> "\n\n" ThematicBreak -> "---\n" -- For table pretty-printing support, take a look at the Markdown.PrettyTables module Table children -> "" TableHeader children -> "" TableBody children -> "" TableRow children -> "" TableCell align children -> "" TableHeaderCell align children -> "" {-| Reduces a block down to anything that can be accumulated. You provide two functions - `accumulate`: Describe how values of type `a` are combined. Examples: `List.concat`, `List.sum`, etc. - `extract`: Descibe how a blocks generate values that are supposed to be accumulated. For example, this can count the amount of headings in a markdown document: reduce { accumulate = List.sum , extract = \block -> case block of Heading _ -> 1 _ -> 0 } Or this extracts code blocks: reduce { accumulate = List.concat , extract = \block -> case block of CodeBlock codeBlock -> [ codeBlock ] _ -> [] } The special thing about this function is how you don't have to worry about accumulating the other generated values recursively. -} reduce : { accumulate : List a -> a, extract : Block a -> a } -> Block a -> a reduce { extract, accumulate } block = let append a b = accumulate [ a, b ] in case block of Heading { children } -> accumulate children |> append (extract block) Paragraph children -> accumulate children |> append (extract block) BlockQuote children -> accumulate children |> append (extract block) Text _ -> extract block CodeSpan _ -> extract block Strong children -> accumulate children |> append (extract block) Emphasis children -> accumulate children |> append (extract block) Strikethrough children -> accumulate children |> append (extract block) Link link -> accumulate link.children |> append (extract block) Image _ -> extract block UnorderedList { items } -> items |> List.concatMap (\(Block.ListItem _ child) -> child) |> accumulate |> append (extract block) OrderedList { items } -> items |> List.concat |> accumulate |> append (extract block) CodeBlock _ -> extract block HardLineBreak -> extract block ThematicBreak -> extract block Table children -> accumulate children |> append (extract block) TableHeader children -> accumulate children |> append (extract block) TableBody children -> accumulate children |> append (extract block) TableRow children -> accumulate children |> append (extract block) TableHeaderCell _ children -> accumulate children |> append (extract block) TableCell _ children -> accumulate children |> append (extract block) {-| There are two ways of thinking about this function: 1. Render a `Block` using the given elm-markdown `Renderer`. 2. Extract a function of type `(Block view -> view)` out of the elm-markdown `Renderer`. This is useful if you want to make use of the utilities present in this library. -} fromRenderer : Renderer view -> Block view -> view fromRenderer renderer markdown = case markdown of Heading info -> renderer.heading info Paragraph children -> renderer.paragraph children BlockQuote children -> renderer.blockQuote children Text content -> renderer.text content CodeSpan content -> renderer.codeSpan content Strong children -> renderer.strong children Emphasis children -> renderer.emphasis children Strikethrough children -> renderer.strikethrough children Link { title, destination, children } -> renderer.link { title = title, destination = destination } children Image imageInfo -> renderer.image imageInfo UnorderedList { items } -> renderer.unorderedList items OrderedList { startingIndex, items } -> renderer.orderedList startingIndex items CodeBlock info -> renderer.codeBlock info HardLineBreak -> renderer.hardLineBreak ThematicBreak -> renderer.thematicBreak Table children -> renderer.table children TableHeader children -> renderer.tableHeader children TableBody children -> renderer.tableBody children TableRow children -> renderer.tableRow children TableHeaderCell maybeAlignment children -> renderer.tableHeaderCell maybeAlignment children TableCell maybeAlignment children -> renderer.tableCell maybeAlignment children {-| Convert a function that works with `Block` to a `Renderer` for use with elm-markdown. (The second parameter is a [`Markdown.Html.Renderer`](/packages/dillonkearns/elm-markdown/3.0.0/Markdown-Html#Renderer)) -} toRenderer : { renderMarkdown : Block view -> view , renderHtml : Markdown.Html.Renderer (List view -> view) } -> Renderer view toRenderer { renderMarkdown, renderHtml } = { heading = Heading >> renderMarkdown , paragraph = Paragraph >> renderMarkdown , blockQuote = BlockQuote >> renderMarkdown , html = renderHtml , text = Text >> renderMarkdown , codeSpan = CodeSpan >> renderMarkdown , strong = Strong >> renderMarkdown , emphasis = Emphasis >> renderMarkdown , strikethrough = Emphasis >> renderMarkdown , hardLineBreak = HardLineBreak |> renderMarkdown , link = \{ title, destination } children -> Link { title = title, destination = destination, children = children } |> renderMarkdown , image = Image >> renderMarkdown , unorderedList = \items -> UnorderedList { items = items } |> renderMarkdown , orderedList = \startingIndex items -> OrderedList { startingIndex = startingIndex, items = items } |> renderMarkdown , codeBlock = CodeBlock >> renderMarkdown , thematicBreak = ThematicBreak |> renderMarkdown , table = Table >> renderMarkdown , tableHeader = TableHeader >> renderMarkdown , tableBody = TableBody >> renderMarkdown , tableRow = TableRow >> renderMarkdown , tableHeaderCell = \maybeAlignment -> TableHeaderCell maybeAlignment >> renderMarkdown , tableCell = \maybeAlignment -> TableCell maybeAlignment >> renderMarkdown } {-| Bump all `Heading` elements by given positive amount of levels. import Markdown.Block as Block bumpHeadings 2 (Heading { level = Block.H1 , rawText = "" , children = [] } ) --> Heading --> { level = Block.H3 --> , rawText = "" --> , children = [] --> } bumpHeadings 1 (Heading { level = Block.H6 , rawText = "" , children = [] } ) --> Heading --> { level = Block.H6 --> , rawText = "" --> , children = [] --> } bumpHeadings -1 (Heading { level = Block.H2 , rawText = "" , children = [] } ) --> Heading --> { level = Block.H2 --> , rawText = "" --> , children = [] --> } -} bumpHeadings : Int -> Block view -> Block view bumpHeadings by markdown = let -- vendored from elm-loop for : Int -> (a -> a) -> a -> a for = let for_ : Int -> Int -> (a -> a) -> a -> a for_ i n f v = if i < n then for_ (i + 1) n f (f v) else v in for_ 0 in case markdown of Heading info -> Heading { info | level = for by bumpHeadingLevel info.level } other -> other -- LOCAL DEFINITIONS allStaticHttp : List (DataSource a) -> DataSource (List a) allStaticHttp = List.foldr (DataSource.map2 (::)) (DataSource.succeed []) bumpHeadingLevel : Block.HeadingLevel -> Block.HeadingLevel bumpHeadingLevel level = case level of Block.H1 -> Block.H2 Block.H2 -> Block.H3 Block.H3 -> Block.H4 Block.H4 -> Block.H5 Block.H5 -> Block.H6 Block.H6 -> Block.H6