Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

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

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, 18, ...>>, {:build, 3}}
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, ...>>, {:markdown_to_html!, 1}}
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, ...>>, {:convert, 4}}
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, 21, ...>>, {:get_post_by_id!, 1}}
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, 24, ...>>, {:render, 1}}
PhoenixPlayground.start(live: MDEx.DemoLive)

19:29:52.699 [info] Running PhoenixPlayground.Endpoint with Bandit 1.7.0 at 127.0.0.1:4000 (http)

19:29:52.704 [info] Access PhoenixPlayground.Endpoint at http://localhost:4000

19:29:52.731 [info] CONNECTED TO Phoenix.LiveView.Socket in 16µs
  Transport: :websocket
  Serializer: Phoenix.Socket.V2.JSONSerializer
  Parameters: %{"vsn" => "2.0.0"}
{:ok, #PID<0.343.0>}

19:29:52.781 [debug] MOUNT PhoenixPlayground.Router.DelegateLive
  Parameters: %{}
  Session: %{}

19:29:52.781 [debug] Replied in 115µs

19:29:52.783 [debug] HANDLE PARAMS in PhoenixPlayground.Router.DelegateLive
  Parameters: %{}

19:29:52.783 [debug] Replied in 61µs

19:29:52.801 [info] GET /

19:29:52.805 [debug] Processing with PhoenixPlayground.Router.DelegateLive.index/2
  Parameters: %{}
  Pipelines: [:browser]

19:29:52.816 [info] Sent 200 in 15ms

19:29:52.843 [info] CONNECTED TO Phoenix.LiveView.Socket in 18µs
  Transport: :websocket
  Serializer: Phoenix.Socket.V2.JSONSerializer
  Parameters: %{"vsn" => "2.0.0"}

19:29:52.844 [debug] MOUNT PhoenixPlayground.Router.DelegateLive
  Parameters: %{}
  Session: %{}

19:29:52.844 [debug] Replied in 101µs

19:29:52.844 [debug] HANDLE PARAMS in PhoenixPlayground.Router.DelegateLive
  Parameters: %{}

19:29:52.844 [debug] Replied in 21µs