neverstew

neverstew

Is it possible to render markdown with Phoenix 1.7?

Hi everyone :wave:

I’ve been playing with the new 1.7 RCs and the (mostly very welcome) changes have left me scratching my head. Any pointers here would be very welcome!

Imagine the basic, scaffolded application that comes with mix phx.new. I would like to be able to drop markdown files inside controllers/page_html and hook them up to a route etc. so that they are eventually served as HTML. Pretty standard markdown → HTML stuff…

I’ve attempted to make this work but failed a couple of times now. I’m not sure if my mental model of the pipeline is off, or it’s something else. If you could have a read of my thought process below and let me know that would be fantastic.

Features

Here are the main things I’d like to do, in a bit more specific terms:

  1. Place markdown files in the relevant directory to be served
  2. Retain any layouts
  3. Make use of Phoenix Components in the markdown

Pipeline

This is definitely the section that I’m least sure about. Here’s what I’m imagining right now:

  1. Read file from disk
  2. Convert markdown string to HTML string with earmark
  3. Compile HTML with the LiveView engine

I can nearly get this working by sneaking in a call to Earmark.as_html! before the compile stage in Phoenix.LiveView.HTMLEngine.

There seems to be somewhat of a conflicting process in steps two and three where they each depend on a small part of each other. e.g. If I wanted to include EEx in the markdown (say, for a simple sum of two numbers) then this wouldn’t work; if I wanted to include a component in the markdown then this wouldn’t be parsed by earmark (it won’t pick it up dots before HTML tags).

Any thoughts appreciated!

Most Liked

tcoopman

tcoopman

An other, it’s not exactly what you want is to write a component like this:

  def markdown(assigns) do
    text = if assigns.text == nil, do: "", else: assigns.text

    markdown_html =
      String.trim(text)
      |> Earmark.as_html!(code_class_prefix: "lang- language-")
      |> Phoenix.HTML.raw()

    assigns = assign(assigns, :markdown, markdown_html)

    ~H"""
    <%= @markdown %>
    """
  end

And then use it like: <.markdown text="your markdown text here" />

10
Post #3
LostKobrakai

LostKobrakai

These eex engines are not composable pieces you can easy make work in any arbirary order. As you noticed Earmark cannot compile things with (h)eex in it and heex cannot compile non html’ish content.
With just EEx you could go the direction of EEx first (it doesn’t care for the content around its tags) to compose a final markdown document and then use Earmark to turn that into html. That won’t give you any integration with heex though.

In the end you’d either need a markdown parser, which can turn markdown with embedded heex/eex tags into html with heex/eex tags and pass that into the heex engine or even a single engine doing both.

I’d suggest a much simpler option, which I’m using for my website: Use NimblePublisher for handling the markdown files. Implement a custom parser, which detects some custom comments <!-- [Component] --> and stores that within the parsed data. When rendering the markdown turn that into a live_render(@conn, …).

stream = Stream.cycle([:html, :live])
    parts = :binary.split(assigns.content, ["<!-- [", "] -->"], [:global])
    assigns = assigns |> assign(:parts, Enum.zip([stream, parts]))

    ~H"""
    <%= for p <- @parts do %>
      <%= case p do %>
        <% {:live, live} -> %>
          <%= live_render(@conn, Module.concat([live])) %>
        <% {:html, html} -> %>
          <%= Phoenix.HTML.raw(html) %>
      <% end %>
    <% end %>
    """
petrus-jvrensburg

petrus-jvrensburg

Thanks for this. Very useful!

Where Next?

Popular in Questions Top

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
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
mgjohns61585
Could someone help me? I’m making my first elixir program, number guessing game. I can’t figure out how to convert the user’s guess from ...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
nobody
How to bind a phoenix app to a specific ip address? could not find anything about that, nowhere, unfortunately, but for me this is quite...
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
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
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
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New

Other popular topics Top

sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 42920 311
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
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
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
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

We're in Beta

About us Mission Statement