I’m using the code/gist from @neuone from here Earmark - add my own class names to the default set of markdown tags - #3 by neuone to customize earmark output…
only caveat is it’s stopped working on latest earmark, so currently locked at {:earmark, “1.4.4”}, and haven’t had time to figure what the issue/solution is, most likely something trivial…
mine looks like this:
you use like this:
some_mark_down
|> Earmark.as_ast()
|> Myapp.Journal.Parser.parsing()
|> Earmark.as_html!()
defmodule Myapp.Journal.Parser do
# defp parse_attr(:body, value) do
# value
# |> Earmark.as_ast()
# |> Myapp.Journal.Parser.parsing()
# |> Earmark.as_html!()
# end
@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", ""}],
"p" => [{"class", "py-2"}],
"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} ->
# IO.inspect(img_attr)
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
if tag == "img" do
IO.inspect(attributes)
attributes
|> Enum.map(fn {key, value} ->
if key == "src" and String.starts_with?(value, "/images") do
{key, value}
else
{key, value}
end
end)
|> IO.inspect()
end
{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