Earmark - add my own class names to the default set of markdown tags

I have improved my Portal.Blog.Parser to be a bit clearer.

Problem
How do I change the default markdown so I can provide my own css styles inline. The reason for doing this is I have preference to write css styles inline.

Goal
• Custom css styles for the default Markdown.
• Still support HTML attributes that can be added to any block-level element, by using the Kramdown syntax.
<p> tags are wrapping the <img> tags with markdown. Would like the <p> tags removed. So the <img> tags are on its own. This is my preference. Nothing wrong with the default.

defmodule Portal.Blog.Parser do
    
    @moduledoc """
    This module will parse the body of a blog post and update the Markdown
    attributes with custom HTML and css attributes.

    This module is used within the Portal.Blog
    """
    def parsing({:ok, results, _option}) do
      Enum.map(results, fn(item) -> 
         parse(item)
         |> Earmark.Transform.transform() 
      end)
    end

    @doc """
    Customize your own css_styles by 
    providing your own tuple of css attributes
    """

    @css_style %{
      "img" =>  [{"class", "mw8 db"}],
      "p"   =>  [{"class", "mw7 lh-copy"}],
      "h1"  =>  [{"class", "mw7 lh-copy"}],
      "h2"  =>  [{"class", "mw7 lh-copy"}],
      "h3"  =>  [{"class", "mw7 lh-copy"}],
      "ul"  =>  [{"class", "mw7 mb4 lh-copy"}],
      "ol"  =>  [{"class", "mw7 lh-copy"}],
      "blockquote" => [{"class", "mw7 lh-copy"}]
      }


    @doc """
    The Markdown wraps all <img> tags with a <p> tag. This function will patttern match any
    <p> tags that might contain a nested <img> tag.

    Then it will take that <img> node and pass it through with some css style if it exists.

    If no img tag is found the <p> is passed through.
    """
    def parse({"p", attributes, children_nodes} = node) do
        first_child = List.first(children_nodes)
        case first_child do
          {"img", img_attr, img_child_nodes} -> parse({"img", img_attr ++ attributes, img_child_nodes})
          _no_img_tag -> {"p", merge_attributes(attributes, Map.get(@css_style, "p")), children_nodes }
        end
    end

    @doc """
    If the `tag` exists in the @css_style this function will merge the existing attributes with
    the new `css_style` attributes.

    If no `tag` exists in the @css_style the node will just pass through.
    """

    def parse({tag, attributes, children_nodes}) do
      {tag, merge_attributes(attributes, Map.get(@css_style, tag)), children_nodes }
    end

    @doc """
    If the css_style is nil just return the attributes
    """
    def merge_attributes(attributes, css_style) when is_nil(css_style) do
      attributes
    end

    @doc """  
    Will concat two list of tuples into one list. Then will merge
    any tuples that have a similar key value in the first index.

    Given the following params these are the expected results.

    # Params example 1
      attributes  = [{"class", "f1"}]
      css_style   = [{"class", "mw7"}]

    iex > merge_attributes(attributes, css_style)
    iex > [{"class", "f1 mw7"}]

    # Params example 2
      attributes    = [{"class", "f1"}, {"id", "headline"}]
      css_style     = [{"class", "mw7"}, {"id", "red"}]

    iex > merge_attributes(attributes, css_style)
    iex > [{"class", "f1 mw7"}, {"id", "headline red"}]

    """
    def merge_attributes(attributes, css_style) do
      attributes ++ css_style 
      |> merge_attributes
    end

    @doc """  
    With one list of tuples some of the items will have duplicate values in the first index.

    This function will merge the duplicates and return a list of tuples.

    # Params 
      attributes = [{"class", "f1"}, {"class", "mw7"}, {"id", "foo"}, {"title", "something"}, {"id", "header"}]

    iex > merge_attributes(attributes)
    iex > [{"class", "f1 mw7"}, {"id", "foo header"}, {"title", "something"}]
    """
    def merge_attributes(attributes) do
      group = 
        Enum.group_by(attributes, fn({key, _value}) -> key end)

      keys = 
        Map.keys(group)

      results = 
        Enum.map(keys, fn(key) -> 
          Enum.reduce(group[key], "", fn({_key, value}, acc) -> 
            acc <> " " <> value 
            |> String.trim
          end)
        end)

      Enum.zip(keys, results)
    end

end

Solution
This solution is working. Maybe the structure can be improved upon. Still learning.

1 Like