From d403675d9abc8afa97c6a499b7ce4cb41b115c2a Mon Sep 17 00:00:00 2001
From: sloane <git@sloanelybutsurely.com>
Date: Wed, 16 Apr 2025 08:08:22 -0400
Subject: [PATCH] remove custom markdown document -> html, tailwind prose

---
 lib/web/components/core_components.ex        |   2 +-
 lib/web/controllers/page_html/home.html.heex |   4 +-
 lib/web/markdown.ex                          | 325 +------------------
 3 files changed, 14 insertions(+), 317 deletions(-)

diff --git a/lib/web/components/core_components.ex b/lib/web/components/core_components.ex
index 5c2d348..43cf78e 100644
--- a/lib/web/components/core_components.ex
+++ b/lib/web/components/core_components.ex
@@ -167,7 +167,7 @@ defmodule Web.CoreComponents do
 
   def markdown(assigns) do
     ~H"""
-    <div class={["markdown-content", @class]}>
+    <div class={["prose", @class]}>
       {Phoenix.HTML.raw(Web.Markdown.to_html(@content))}
     </div>
     """
diff --git a/lib/web/controllers/page_html/home.html.heex b/lib/web/controllers/page_html/home.html.heex
index 12e1361..dda1b4c 100644
--- a/lib/web/controllers/page_html/home.html.heex
+++ b/lib/web/controllers/page_html/home.html.heex
@@ -16,8 +16,8 @@
               navigate={Web.Paths.public_blog_path(blog)}
               class="flex justify-between items-center hover:bg-gray-50 -mx-2 px-2 py-1 rounded"
             >
-              <h3 class="p-name u-url">{blog.title}</h3>
-              <div class="text-gray-500 dt-published">
+              <h3 class="p-name u-url truncate">{blog.title}</h3>
+              <div class="text-gray-500 dt-published flex-shrink-0">
                 <.timex value={Core.Posts.publish_date_time(blog)} format="{Mfull} {D}, {YYYY}" />
               </div>
             </.link>
diff --git a/lib/web/markdown.ex b/lib/web/markdown.ex
index 784aa66..0ac9866 100644
--- a/lib/web/markdown.ex
+++ b/lib/web/markdown.ex
@@ -1,323 +1,20 @@
 defmodule Web.Markdown do
   @moduledoc """
-  Converts markdown to HTML using MDEx, with custom rendering to add Tailwind CSS classes.
-  Uses iodata for efficient string generation.
+  Converts markdown to HTML using MDEx.
   """
 
-  alias MDEx.Document
-
-  @doc """
-  Converts markdown to HTML with syntax highlighting, additional extensions,
-  and appropriate Tailwind CSS classes for styling.
-  """
   def to_html(markdown) when is_binary(markdown) do
-    # First parse the document to get the AST
-    {:ok, document} =
-      MDEx.parse_document(markdown,
-        extension: [
-          strikethrough: true,
-          table: true,
-          autolink: true,
-          tasklist: true,
-          footnotes: true
-        ],
-        features: [syntax_highlight_theme: "catppuccin_latte"]
-      )
-
-    # Render the document to HTML with Tailwind classes
-    render_document(document)
+    MDEx.to_html!(markdown,
+      extension: [
+        strikethrough: true,
+        table: true,
+        autolink: true,
+        tasklist: true,
+        footnotes: true
+      ],
+      features: [syntax_highlight_theme: "catppuccin_latte"]
+    )
   end
 
   def to_html(nil), do: ""
-
-  # Core rendering function for the document
-  defp render_document(%Document{} = document) do
-    document.nodes
-    |> Enum.map(&render_node(&1, nil))
-  end
-
-  # Heading (h1-h6) with anchor links
-  defp render_node(%MDEx.Heading{level: level} = node, _parent) do
-    tag = "h#{level}"
-
-    class =
-      case level do
-        1 -> "text-3xl font-bold mt-6 mb-4 group"
-        2 -> "text-2xl font-bold mt-5 mb-3 group"
-        3 -> "text-xl font-bold mt-4 mb-2 group"
-        4 -> "text-lg font-bold mt-3 mb-2 group"
-        5 -> "text-base font-bold mt-3 mb-2 group"
-        6 -> "text-sm font-bold mt-3 mb-2 group"
-      end
-
-    # Generate a slug from the text content for the ID
-    content = Enum.map(node.nodes, &render_node(&1, node))
-    content_text = extract_text_content(node)
-    slug = Slug.slugify(content_text)
-
-    [
-      "<",
-      tag,
-      " id=\"",
-      slug,
-      "\" class=\"",
-      class,
-      " relative\">",
-      "<a href=\"#",
-      slug,
-      "\" class=\"absolute -left-5 text-gray-400 opacity-0 group-hover:opacity-100 transition-opacity\">¶</a>",
-      content,
-      "</",
-      tag,
-      ">"
-    ]
-  end
-
-  # Paragraph
-  defp render_node(%MDEx.Paragraph{} = node, _parent) do
-    content = Enum.map(node.nodes, &render_node(&1, node))
-    ["<p>", content, "</p>"]
-  end
-
-  # Text
-  defp render_node(%MDEx.Text{literal: literal}, _parent) do
-    literal
-  end
-
-  # Strong (bold)
-  defp render_node(%MDEx.Strong{} = node, _parent) do
-    content = Enum.map(node.nodes, &render_node(&1, node))
-    ["<strong>", content, "</strong>"]
-  end
-
-  # Emphasis (italic)
-  defp render_node(%MDEx.Emph{} = node, _parent) do
-    content = Enum.map(node.nodes, &render_node(&1, node))
-    ["<em>", content, "</em>"]
-  end
-
-  # Strikethrough
-  defp render_node(%MDEx.Strikethrough{} = node, _parent) do
-    content = Enum.map(node.nodes, &render_node(&1, node))
-    ["<del>", content, "</del>"]
-  end
-
-  # Link
-  defp render_node(%MDEx.Link{url: url, title: title} = node, _parent) do
-    content = Enum.map(node.nodes, &render_node(&1, node))
-    title_attr = if title, do: [" title=\"", title, "\""], else: []
-
-    [
-      "<a href=\"",
-      url,
-      "\"",
-      title_attr,
-      " class=\"text-blue-600 hover:text-blue-800 hover:underline\">",
-      content,
-      "</a>"
-    ]
-  end
-
-  # Image
-  defp render_node(%MDEx.Image{url: url, title: title} = node, _parent) do
-    alt = Enum.map(node.nodes, &render_node(&1, node))
-    title_attr = if title, do: [" title=\"", title, "\""], else: []
-
-    [
-      "<img src=\"",
-      url,
-      "\" alt=\"",
-      alt,
-      "\"",
-      title_attr,
-      " class=\"max-w-full my-4 rounded\">"
-    ]
-  end
-
-  # Code (inline)
-  defp render_node(%MDEx.Code{literal: literal}, _parent) do
-    ["<code class=\"bg-gray-100 rounded px-1 py-0.5 text-sm\">", literal, "</code>"]
-  end
-
-  # Code blocks - use MDEx's built-in HTML rendering to preserve syntax highlighting
-  defp render_node(%MDEx.CodeBlock{} = node, _parent) do
-    # Use MDEx's built-in HTML rendering for code blocks to preserve syntax highlighting
-    {:ok, html} =
-      MDEx.to_html(
-        %MDEx.Document{nodes: [node]},
-        extension: [],
-        features: [syntax_highlight_theme: "catppuccin_latte"]
-      )
-
-    # Return the HTML as is since it's already properly formatted with syntax highlighting
-    String.replace(html, ~s|class="autumn-hl"|, ~s|class="autumn-hl p-2 rounded"|)
-  end
-
-  # Lists - Bullet
-  defp render_node(%MDEx.List{list_type: :bullet} = node, _parent) do
-    content = Enum.map(node.nodes, &render_node(&1, node))
-    ["<ul class=\"list-disc pl-6 my-4\">", content, "</ul>"]
-  end
-
-  # Lists - Ordered
-  defp render_node(%MDEx.List{list_type: :ordered} = node, _parent) do
-    content = Enum.map(node.nodes, &render_node(&1, node))
-    ["<ol class=\"list-decimal pl-6 my-4\">", content, "</ol>"]
-  end
-
-  # List items
-  defp render_node(%MDEx.ListItem{} = node, _parent) do
-    content = Enum.map(node.nodes, &render_node(&1, node))
-    ["<li class=\"mb-1\">", content, "</li>"]
-  end
-
-  # Task items (checkboxes)
-  defp render_node(%MDEx.TaskItem{checked: checked} = node, _parent) do
-    checkbox =
-      if checked do
-        "<input type=\"checkbox\" checked disabled class=\"rounded mr-1.5 text-blue-600\">"
-      else
-        "<input type=\"checkbox\" disabled class=\"rounded mr-1.5 text-blue-600\">"
-      end
-
-    content = Enum.map(node.nodes, &render_node(&1, node))
-
-    [
-      "<li class=\"mb-1 flex items-start\">",
-      checkbox,
-      "<div class=\"inline\">",
-      content,
-      "</div>",
-      "</li>"
-    ]
-  end
-
-  # Blockquote
-  defp render_node(%MDEx.BlockQuote{} = node, _parent) do
-    content = Enum.map(node.nodes, &render_node(&1, node))
-
-    [
-      "<blockquote class=\"border-l-4 border-gray-300 pl-4 py-1 my-4 italic\">",
-      content,
-      "</blockquote>"
-    ]
-  end
-
-  # Horizontal Rule
-  defp render_node(%MDEx.ThematicBreak{}, _parent) do
-    "<hr class=\"border-t border-gray-300 my-6\">"
-  end
-
-  # Table
-  defp render_node(%MDEx.Table{} = node, _parent) do
-    content = Enum.map(node.nodes, &render_node(&1, node))
-
-    [
-      "<div class=\"overflow-x-auto my-4\"><table class=\"min-w-full divide-y divide-gray-200\">",
-      content,
-      "</table></div>"
-    ]
-  end
-
-  # Table row
-  defp render_node(%MDEx.TableRow{header: true} = node, _parent) do
-    content = Enum.map(node.nodes, &render_node(&1, node))
-    ["<tr class=\"bg-gray-50 border-b border-gray-200\">", content, "</tr>"]
-  end
-
-  defp render_node(%MDEx.TableRow{header: false} = node, _parent) do
-    content = Enum.map(node.nodes, &render_node(&1, node))
-    ["<tr class=\"border-b border-gray-200\">", content, "</tr>"]
-  end
-
-  # Table cell in header row
-  defp render_node(%MDEx.TableCell{} = node, %MDEx.TableRow{header: true}) do
-    content = Enum.map(node.nodes, &render_node(&1, node))
-
-    [
-      "<th class=\"px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider\">",
-      content,
-      "</th>"
-    ]
-  end
-
-  # Table cell in normal row
-  defp render_node(%MDEx.TableCell{} = node, _parent) do
-    content = Enum.map(node.nodes, &render_node(&1, node))
-    ["<td class=\"px-6 py-4 whitespace-nowrap text-sm\">", content, "</td>"]
-  end
-
-  # HTML blocks (pass through)
-  defp render_node(%MDEx.HtmlBlock{literal: literal}, _parent) do
-    literal
-  end
-
-  # HTML inline (pass through)
-  defp render_node(%MDEx.HtmlInline{literal: literal}, _parent) do
-    literal
-  end
-
-  # Soft break (line break that doesn't create new paragraph)
-  defp render_node(%MDEx.SoftBreak{}, _parent) do
-    "\n"
-  end
-
-  # Hard break (explicit line break with <br>)
-  defp render_node(%MDEx.LineBreak{}, _parent) do
-    "<br>"
-  end
-
-  # Footnote reference
-  defp render_node(%MDEx.FootnoteReference{name: name}, _parent) do
-    [
-      "<sup class=\"footnote-ref\">",
-      "<a href=\"#fn-",
-      name,
-      "\" id=\"fnref-",
-      name,
-      "\" class=\"text-blue-600 hover:text-blue-800\">",
-      name,
-      "</a>",
-      "</sup>"
-    ]
-  end
-
-  # Footnote definition
-  defp render_node(%MDEx.FootnoteDefinition{name: name} = node, _parent) do
-    content = Enum.map(node.nodes, &render_node(&1, node))
-
-    [
-      "<div class=\"footnote flex flex-row\" id=\"fn-",
-      name,
-      "\">",
-      "<p class=\"my-1 flex\">",
-      "<span class=\"mr-2 flex-shrink-0\"><sup>",
-      name,
-      ".</sup></span>",
-      "<span class=\"inline\">",
-      content,
-      " <a href=\"#fnref-",
-      name,
-      "\" class=\"text-blue-600 hover:text-blue-800 text-sm\">↩</a>",
-      "</span>",
-      "</p>",
-      "</div>"
-    ]
-  end
-
-  # Extract plain text from a node tree (for generating slugs)
-  defp extract_text_content(node) do
-    cond do
-      is_map(node) && Map.has_key?(node, :literal) && is_binary(node.literal) ->
-        node.literal
-
-      is_map(node) && Map.has_key?(node, :nodes) ->
-        node.nodes
-        |> Enum.map(&extract_text_content/1)
-        |> Enum.join("")
-
-      true ->
-        ""
-    end
-  end
 end