Powered by AppSignal & Oban Pro

NimblePublisher

examples/nimble_publisher.livemd

NimblePublisher

Mix.install([
  {:mdex, "~> 0.8"},
  {:phoenix_live_view, "~> 1.0"},
  {:phoenix_playground, "~> 0.1"},
  {:nimble_publisher, "~> 1.1"},
  {:phoenix_html, "~> 4.2"}
])

Example

By @PJUllrich

defmodule MDEx.Posts.Post do
  @enforce_keys [:id, :title, :date, :body]
  defstruct [:id, :title, :date, :body]

  def build(filepath, attrs, body) do
    [year, month, day, id] =
      filepath |> Path.rootname() |> Path.split() |> List.last() |> String.split("-", parts: 4)

    id =
      id
      |> String.trim_trailing(".md")
      |> String.downcase()

    date = Date.from_iso8601!("#{year}-#{month}-#{day}")

    struct!(__MODULE__, Map.merge(attrs, %{id: id, date: date, body: body}))
  end
end
{:module, MDEx.Posts.Post, <<70, 79, 82, 49, 0, 0, 19, ...>>, ...}
defmodule MDEx.Posts.Parser do
  def parse(_path, contents) do
    [header, markdown_body] = String.split(contents, "---\n", trim: true, parts: 2)

    {%{} = attrs, _} = Code.eval_string(header, [])
    html_body = markdown_to_html!(markdown_body)

    {attrs, html_body}
  end

  defp markdown_to_html!(markdown_body) do
    MDEx.to_html!(markdown_body,
      syntax_highlight: [formatter: {:html_inline, theme: "github_dark"}],
      extension: [
        strikethrough: true,
        underline: true,
        tagfilter: true,
        table: true,
        autolink: true,
        tasklist: true,
        footnotes: true,
        shortcodes: true
      ],
      parse: [
        smart: true,
        relaxed_tasklist_matching: true,
        relaxed_autolinks: true
      ],
      render: [
        github_pre_lang: true,
        escape: true,
        hardbreaks: true
      ]
    )
  end
end
{:module, MDEx.Posts.Parser, <<70, 79, 82, 49, 0, 0, 11, ...>>, ...}
defmodule MDEx.Posts.HTMLConverter do
  # ⚠️ Important ⚠️
  # You need to provide a custom converter, because otherwise NimblePublisher
  # will apply their default markdown -> HTML conversion which will
  # interfere with MDEx's conversion.
  def convert(_extname, body, _attrs, _opts), do: body
end
{:module, MDEx.Posts.HTMLConverter, <<70, 79, 82, 49, 0, 0, 7, ...>>, ...}
defmodule MDEx.Posts do
  use NimblePublisher,
    # Update this filepath to your application
    # and move the `posts` folder inside your `priv` folder.
    #
    # from: Application.app_dir(:my_app, "priv/posts/*.md"),
    from: Path.join([Path.absname(__DIR__), "posts", "*.md"]),
    build: MDEx.Posts.Post,
    parser: MDEx.Posts.Parser,
    html_converter: MDEx.Posts.HTMLConverter,
    as: :posts,
    highlighters: []

  @posts Enum.sort_by(@posts, &amp; &amp;1.date, {:desc, Date})

  def all_posts, do: @posts

  defmodule NotFoundError do
    defexception [:message, plug_status: 404]
  end

  def get_post_by_id!(id) do
    Enum.find(all_posts(), &amp;(&amp;1.id == id)) ||
      raise NotFoundError, "post with id=#{id} not found"
  end
end
{:module, MDEx.Posts, <<70, 79, 82, 49, 0, 0, 26, ...>>, ...}
defmodule MDEx.DemoLive do
  use Phoenix.LiveView
  import Phoenix.HTML

  def mount(%{"id" => post_id}, _session, socket) do
    post = MDEx.Posts.get_post_by_id!(post_id)
    {:ok, assign(socket, :post, post)}
  end

  # Show the first blog post by default.
  # In your app, you'd show an overview of the blog posts instead.
  def mount(_params, session, socket) do
    mount(%{"id" => "example-post"}, session, socket)
  end

  def render(assigns) do
    ~H"""
    

{@post.title}

{@post.date}

{raw(@post.body)} """
end end
{:module, MDEx.DemoLive, <<70, 79, 82, 49, 0, 0, 25, ...>>, ...}
PhoenixPlayground.start(live: MDEx.DemoLive)

16:37:47.791 [info] Running PhoenixPlayground.Endpoint with Bandit 1.10.1 at 127.0.0.1:4000 (http)

16:37:47.791 [info] Access PhoenixPlayground.Endpoint at http://localhost:4000
{:ok, #PID<0.4689.0>}

16:37:47.905 [info] GET /

16:37:47.908 [debug] Processing with PhoenixPlayground.Router.DelegateLive.__live__/0
  Parameters: %{}
  Pipelines: [:browser]

16:37:47.927 [info] Sent 200 in 21ms

16:37:47.949 [info] CONNECTED TO Phoenix.LiveView.Socket in 11µs
  Transport: :websocket
  Serializer: Phoenix.Socket.V2.JSONSerializer
  Parameters: %{"vsn" => "2.0.0"}

16:37:47.974 [debug] MOUNT PhoenixPlayground.Router.DelegateLive
  Parameters: %{}
  Session: %{}

16:37:47.974 [debug] Replied in 109µs

16:37:47.974 [debug] HANDLE PARAMS in PhoenixPlayground.Router.DelegateLive
  Parameters: %{}

16:37:47.974 [debug] Replied in 58µs