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…

First Post!

RobertDober

RobertDober

It is back in Earmark so you should not have any problem. No it is not floki but homemade. Transform.transform

I am no frontend guy by any means, therefore I do not know what exactly is needed, but seems you are doing fine above, no?

As I am not maintaining Earmark I cannot change the code which might be a little bit too complicated but basically use

   something = Proxy.as_ast(markdown)
   modified = do_something_with(something)
   Transform.transform(modified)

Of course you can using the postprocessor as above if it is enough

HTH

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

electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
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
jononomo
For some reason my phoenix channels are working for me in my local dev environment, but as soon as I deploy via Docker, I get a 403 error...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New

Other popular topics Top

nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New

We're in Beta

About us Mission Statement