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.