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

srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
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
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list....
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
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
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

Other popular topics Top

Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3268 119930 1237
New
peerreynders
Manning 2016 Halloween weekend sale via Deal of the Day Friday, October 28 - Half off all MEAPs - code WM102816LT Saturday, October 29 ...
326 29600 154
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 record...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
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
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
magnetic
Hey :wave:t3: Elixir community, I’ve been learning Elixir, and working on some side projects. My editor of choice is VSCode, and althoug...
New

We're in Beta

About us Mission Statement