matheus23 / elm-markdown-transforms / Markdown.AnchorValidation

Validate internal markdown links

These internal markdown links (what appear when you link to sections in wikipedia, for example) are called 'Anchor Links'.

Check out the example that uses this module and it's source code.


type alias Validated view =
{ validate : Anchors -> Result Error view
, words : List String
, generatedAnchors : Anchors 
}

The type we use for folds. You don't need to worry about it. You can use the API for working with it exclusively. That is, resolve, fold, the various maps and validateLink.

If you're curious what this type is about: Anchor link checking works in two phases:

  1. The markdown gets reduced to the list of words and all used anchors at the same time (the words and generatedAnchors fields).
  2. With the information about what anchors there are in the markdown, we can now render it, validating that our links are not invalid at the same time (the validate field).


type alias Anchors =
List String

We model all existing anchors simply as a list of strings.


type Error
    = DuplicatedAnchors (List Anchors)
    | InvalidAnchorLink String

Anchor link checking can go wrong in two ways:

Render with Validation

fold : Markdown.Scaffolded.Block (Validated view) -> Validated (Markdown.Scaffolded.Block view)

Fold a Validated value through your block.

If you don't know how to use this function, take a look at the example.

If you want to know more about what folds are, take a look at the [docs for Scaffolded].

map : (a -> b) -> Validated a -> Validated b

Map over the view inside a Validated value.

You can use this to construct a view function

myViewReducer : Scaffolded.Block (Html Msg) -> Html Msg
myViewReducer =
    -- or any other implementation
    Scaffolded.reduceHtml []

viewValidated : Scaffolded.Block (Validated (Html Msg)) -> Validated (Html Msg)
viewValidated block =
    block
        |> fold
        -- Now, we have a `Validated (Scaffolded.Block (Html Msg))`
        |> map myViewReducer

mapWithGeneratedAnchor : (String -> a -> b) -> Validated a -> Validated b

Map over the Validated value and generate an anchor at the same time!

viewValidated : Scaffolded.Block (Validated (Html Msg)) -> Validated (Html Msg)
viewValidated block =
    case block of
        Scaffolded.Heading _ ->
            block
                |> fold
                |> mapWithGeneratedAnchor
                    (\anchor -> Scaffolded.reduceHtml [ Attr.id anchor ])

        _ ->
            block
                |> fold
                |> map (Scaffolded.reduceHtml [])

(See also the [docs for map].)

The extracted words from markdown here are transformed into an 'anchor', which is a string, consisting only of the alphanumeric characters of the contained markdown joined by dashes.

mapWithCustomGeneratedAnchor : (List String -> String) -> (String -> a -> b) -> Validated a -> Validated b

Same as [mapWithGeneratedAnchor], but you decide how to extract an anchor link from the words inside markdown.

validateLink : String -> Validated view -> Validated view

Validate given anchor link, to make sure it exists.

viewValidated : Scaffolded.Block (Validated (Html Msg)) -> Validated (Html Msg)
viewValidated block =
    case block of
        Scaffolded.Link { destination } ->
            block
                |> fold
                |> validateLink destination
                |> map (Scaffolded.foldHtml [])

        Scaffolded.Heading _ ->
            block
                |> fold
                |> mapWithGeneratedAnchor
                    (\anchor -> Scaffolded.foldHtml [ Attr.id anchor ])

        _ ->
            block
                |> fold
                |> map (Scaffolded.foldHtml [])

(See also the docs for map and docs for mapWithGeneratedAnchor.)

liftHtmlRenderer : Markdown.Html.Renderer (List a -> a) -> Markdown.Html.Renderer (List (Validated a) -> Validated a)

If you just started building in your anchor validation, but haven't updated your Html renderers to reduce to Validated values, just use this function to lift them automatically.

Resolve Validation

resolve : (Error -> error) -> List (Validated a) -> Result error (List a)

Resolve validation errors

errorToString : Error -> String

Generate fairly descriptive error messages