marschro

marschro

Style formatting for nested elements with earmark or earmark_parser

Hello,

so I started to integrate markdown editing in my app and I ended up integrating earmark, which works fine for me.

Currently I have a utility function that I can call everywhere in my LiveViews in order to generate html from markdown.

Here how his looks ( I reuse styles, defined in my CoreComponents):

@doc """
  parse markdown to styled html

  """
  def markdown(nil), do: nil

  def markdown(content) do
    case Earmark.Parser.as_ast(content) do
      {:ok, ast, _warnings} ->
        ast
        |> parse_ast()
        |> Earmark.Transform.transform()

      {:error, _ast, warnings} ->
        {:warning, _id, msg} = List.first(warnings)
        "Invalid markup: #{msg}"
    end
  end

  defp parse_ast(ast) do
    ast
    |> Earmark.Transform.map_ast(fn node -> parse_node(node) end, ignore_strings: true)
    |> List.flatten()
  end

  defp parse_node({_tag, _attrs, _children_or_content, _meta} = node) do
    add = fn class_list ->
      fn node -> Earmark.AstTools.merge_atts_in_node(node, class: Enum.join(class_list, " ")) end
    end

    processors = [
      {"h1", add.(PortalWeb.CoreComponents.typography(:h1))},
      {"h2", add.(PortalWeb.CoreComponents.typography(:h2))},
      {"h3", add.(PortalWeb.CoreComponents.typography(:h3))},
      {"h4", add.(PortalWeb.CoreComponents.typography(:h4))},
      {"p", add.(PortalWeb.CoreComponents.typography(:p))},
      {"ul", add.(PortalWeb.CoreComponents.typography(:ul))},
      {"ol", add.(PortalWeb.CoreComponents.typography(:ol))},
      {"li", add.(PortalWeb.CoreComponents.typography(:li))},
      {"a", add.(PortalWeb.CoreComponents.typography(:a))},
      {"strong", add.(PortalWeb.CoreComponents.typography(:strong))},
      {"em", add.(PortalWeb.CoreComponents.typography(:em))},
      {"u", add.(PortalWeb.CoreComponents.typography(:u))},
      {"img", add.(PortalWeb.CoreComponents.typography(:img))},
      {"blockquote", add.(PortalWeb.CoreComponents.typography(:blockquote))},
      {"hr", add.(PortalWeb.CoreComponents.typography(:hr))},
      {"code", add.(PortalWeb.CoreComponents.typography(:code))},
      {"pre", add.(PortalWeb.CoreComponents.typography(:pre))}
    ]

    postprocessor =
      Earmark.Options.make_options!(registered_processors: processors)
      |> Earmark.Transform.make_postprocessor()

    postprocessor.(node)
  end

No I have two questions:

  1. I ran into the thing, that earmark_parser was extracted from earmark. So if I only use earmark_parser - how do I generate html markup from the generated ast ? With floki? How is that done.
  2. Also I struggle with styling nested elements like lists in list. Has anyone a good example how that can be achieved? Currently I only can add classes to every
  3. element but cannot differntiate if this is a list of 1st or second level… (don’t want to do that via css but with tailwind classes…)

Any advice appreciated :slight_smile:

… also how is such a cool editor done like this one I am typing in…

Most Liked

RobertDober

RobertDober

There is no built in way to do this easily as far as I remember, the idea is that you need to transform the ast yourself, here is a draft of how this should work

    defp add_level_att(tag, atts, inner, meta, level) do
      {
        tag,
        Keyword.put(atts, :class, "list-level-#{level}"),
        recursive_map(inner, level+1),
        meta
      }
    end

    defp recursive_map(ast, level)
    defp recursive_map(list, level) when is_list(list), do: Enum.map(list, &recursive_map(&1, level))
    defp recursive_map(leaf, _) when is_binary(leaf), do: leaf
    defp recursive_map({"ol", atts, inner, meta}, level), do: add_level_att("ol", atts, inner, meta, level)
    defp recursive_map({"ul", atts, inner, meta}, level), do: add_level_att("ul", atts, inner, meta, level)
    defp recursive_map({tag, atts, inner, meta}, level), do: {tag, atts, recursive_map(inner, level), meta}

Where Next?

Popular in Questions Top

marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
stefanluptak
Hello everybody, usually, I use a 29" ultra-wide monitor for VSCode which can easily accomodate explorer (files panel) + file with code ...
New
lucidguppy
I have a super simple question about elixir - how would I take a file like this foo bar baz and output a new file that enumerates th...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New

Other popular topics Top

JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a > b) do {:ok, "a"} end if (a < b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New
sergio
Kind of like when jquery came out, it was super necessary. Existing drag and drop libraries have a bunch of baggage to support old browse...
New

We're in Beta

About us Mission Statement