# NimblePublisher ```elixir Mix.install([ {:mdex, "~> 0.11"}, {:phoenix_live_view, "~> 1.0"}, {:phoenix_playground, "~> 0.1"}, {:nimble_publisher, "~> 1.1"}, {:phoenix_html, "~> 4.2"}, {:req_embed, "~> 0.3"} ]) ``` ## Example By @PJUllrich ```elixir 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, ...>>, ...} ``` ```elixir 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) env = __ENV__ ast = EEx.compile_string( html_body, engine: Phoenix.LiveView.TagEngine, file: env.file, line: env.line + 1, caller: env, indentation: 0, source: html_body, tag_handler: Phoenix.LiveView.HTMLEngine ) assigns = %{} {rendered, _} = Code.eval_quoted(ast, [assigns: assigns], env) html_body = rendered |> Phoenix.HTML.Safe.to_iodata() |> IO.iodata_to_binary() {attrs, html_body} end defp markdown_to_html!(markdown_body) do MDEx.to_html!(markdown_body, [ extension: [ strikethrough: true, table: true, autolink: false, tasklist: true, superscript: true, footnotes: true, description_lists: true, # need both multiline block quotes and alerts to enable github/gitlab multiline alerts multiline_block_quotes: true, alerts: true, math_dollars: true, math_code: true, shortcodes: true, underline: true, spoiler: true, phoenix_heex: true ], parse: [ relaxed_tasklist_matching: true, relaxed_autolinks: true ], render: [ unsafe: true, escape: false, # github_pre_lang and full_info_string are required to enable code block decorators github_pre_lang: true, full_info_string: true ] ] ) end end ``` ``` {:module, MDEx.Posts.Parser, <<70, 79, 82, 49, 0, 0, 40, ...>>, ...} ``` ```elixir 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, ...>>, ...} ``` ```elixir 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, & &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(), &(&1.id == id)) || raise NotFoundError, "post with id=#{id} not found" end end ``` ``` {:module, MDEx.Posts, <<70, 79, 82, 49, 0, 0, 27, ...>>, ...} ``` ```elixir 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"""