defmodule CFSync.RichTextRenderer do
@moduledoc """
Phoenix components for CFSync RichText rendering.
BEWARE OF WHITESPACE
It is essential to avoid adding whitespace to the text content.
We use white-space: pre-line; to render line breaks added to rich text.
Adding line breaks here in the markup would propagate blank lines to the pages.
"""
use Phoenix.Component
import Phoenix.HTML, only: [raw: 1]
def render(assigns) do
assigns =
assigns
|> assign_new(:class, fn -> "" end)
|> assign_new(:delegate, fn -> false end)
if assigns.delegate do
{:module, _mod} = Code.ensure_loaded(assigns.delegate)
end
~H"""
<div class={@class}><.rt_node_content {assigns} node={@content} /></div>
"""
end
defp rt_node_content(assigns) do
~H"""
<%= for node <- @node.content do %><.rt_node_switch {assigns} node={node}><.rt_node_content {assigns} node={node} /></.rt_node_switch><% end %>
"""
end
defp rt_node_switch(%{node: %{type: "text"}} = assigns), do: rt_node(assigns)
defp rt_node_switch(%{node: %{type: type}} = assigns) do
if assigns.delegate && function_exported?(assigns.delegate, type, 1) do
apply(assigns.delegate, type, [assigns])
else
rt_node(assigns)
end
end
defp rt_mark_switch(assigns, mark) do
if assigns.delegate && function_exported?(assigns.delegate, mark, 1) do
apply(assigns.delegate, mark, [assigns])
else
case mark do
:bold -> bold(assigns)
:italic -> italic(assigns)
:underline -> underline(assigns)
:code -> code(assigns)
:superscript -> superscript(assigns)
:subscript -> subscript(assigns)
:strikethrough -> strikethrough(assigns)
end
end
end
defp rt_text_switch(assigns) do
if assigns.delegate && function_exported?(assigns.delegate, :text_content, 1) do
apply(assigns.delegate, :text_content, [assigns])
else
assigns.node.value
end
end
defp bold(assigns) do
~H"<b><%= render_slot @inner_block %></b>"
end
defp italic(assigns) do
~H"<i><%= render_slot @inner_block %></i>"
end
defp underline(assigns) do
~H"<u><%= render_slot @inner_block %></u>"
end
defp code(assigns) do
~H"<code><%= render_slot @inner_block %></code>"
end
defp superscript(assigns) do
~H"<sup><%= render_slot @inner_block %></sup>"
end
defp subscript(assigns) do
~H"<sub><%= render_slot @inner_block %></sub>"
end
defp strikethrough(assigns) do
~H"<s><%= render_slot @inner_block %></s>"
end
defp rt_node(%{node: %{type: :text}} = assigns) do
alias Phoenix.LiveView.TagEngine
for mark <- assigns.node.marks, reduce: ~H"<%= raw rt_text_switch(assigns) %>" do
acc ->
%{
assigns
| inner_block: [
%{
__slot__: :inner_block,
inner_block: TagEngine.inner_block(:inner_block, do: acc)
}
]
}
|> rt_mark_switch(mark)
end
end
defp rt_node(%{node: %{type: :paragraph}} = assigns) do
~H"""
<p><%= render_slot @inner_block %></p>
"""
end
defp rt_node(%{node: %{type: :heading_1}} = assigns) do
~H"""
<h1><%= render_slot @inner_block %></h1>
"""
end
defp rt_node(%{node: %{type: :heading_2}} = assigns) do
~H"""
<h2><%= render_slot @inner_block %></h2>
"""
end
defp rt_node(%{node: %{type: :heading_3}} = assigns) do
~H"""
<h3><%= render_slot @inner_block %></h3>
"""
end
defp rt_node(%{node: %{type: :heading_4}} = assigns) do
~H"""
<h4><%= render_slot @inner_block %></h4>
"""
end
defp rt_node(%{node: %{type: :heading_5}} = assigns) do
~H"""
<h5><%= render_slot @inner_block %></h5>
"""
end
defp rt_node(%{node: %{type: :heading_6}} = assigns) do
~H"""
<h6><%= render_slot @inner_block %></h6>
"""
end
defp rt_node(%{node: %{type: :ol_list}} = assigns) do
~H"""
<ol><%= render_slot @inner_block %></ol>
"""
end
defp rt_node(%{node: %{type: :ul_list}} = assigns) do
~H"""
<ul><%= render_slot @inner_block %></ul>
"""
end
defp rt_node(%{node: %{type: :list_item}} = assigns) do
~H"""
<li><%= render_slot @inner_block %></li>
"""
end
defp rt_node(%{node: %{type: :table}} = assigns) do
~H"""
<table><%= render_slot @inner_block %></table>
"""
end
defp rt_node(%{node: %{type: :table_row}} = assigns) do
~H"""
<tr><%= render_slot @inner_block %></tr>
"""
end
defp rt_node(%{node: %{type: :table_header_cell}} = assigns) do
~H"""
<th><%= render_slot @inner_block %></th>
"""
end
defp rt_node(%{node: %{type: :table_cell}} = assigns) do
~H"""
<td><%= render_slot @inner_block %></td>
"""
end
defp rt_node(%{node: %{type: :hr}} = assigns) do
~H"""
<hr />
"""
end
defp rt_node(%{node: %{type: :quote}} = assigns) do
~H"""
<blockquote><%= render_slot @inner_block %></blockquote>
"""
end
defp rt_node(%{node: %{type: :hyperlink}} = assigns) do
~H"""
<a href={@node.uri}><%= render_slot @inner_block %></a>
"""
end
defp rt_node(%{node: %{type: _unimplemented_type}} = assigns) do
~H"""
<div style="border: 2px solid red;">
<div style="background-color: red; color: white; font-weight: bold; padding: .25rem;">
Missing component for <%= @node.type %>
</div>
<div><%= render_slot @inner_block %></div>
</div>
"""
end
end