86 lines
2.3 KiB
Elixir
86 lines
2.3 KiB
Elixir
defmodule PreDotHn.Markdown do
|
|
@default_opts [pure_links: true, wikilinks: true, inner_html: false]
|
|
|
|
def render(source, opts \\ []) do
|
|
opts = Keyword.merge(@default_opts, opts, fn _key, _default_value, value -> value end)
|
|
|
|
inner_html = Keyword.get(opts, :inner_html, false)
|
|
|
|
EarmarkParser.as_ast(source || "", opts)
|
|
|> case do
|
|
{:ok, ast, _} ->
|
|
ast
|
|
|> Earmark.Transform.map_ast(&transformer/1)
|
|
|> walk()
|
|
|> Enum.map(&maybe_remove_para(&1, inner_html))
|
|
|
|
other ->
|
|
other
|
|
end
|
|
|> Earmark.Transform.transform()
|
|
end
|
|
|
|
defp transformer({"a", attrs, ignored, meta}) do
|
|
href = Enum.find_value(attrs, "", fn {key, value} -> key == "href" && value end)
|
|
href_uri = URI.parse(href)
|
|
|
|
is_internal = href_uri.host in [host(), nil]
|
|
|
|
attrs =
|
|
if is_internal do
|
|
attrs
|
|
else
|
|
[{"target", "_blank"} | attrs]
|
|
end
|
|
|
|
{"a", attrs, ignored, meta}
|
|
end
|
|
|
|
defp transformer(other), do: other
|
|
|
|
def walk(list) when is_list(list), do: Enum.map(list, &walk/1)
|
|
|
|
def walk(binary) when is_binary(binary), do: binary
|
|
|
|
def walk({"pre", _attrs, [{"code", attrs, [children], _}], _meta}) do
|
|
lang = Earmark.AstTools.find_att_in_node(attrs, "class")
|
|
|
|
walk(highlight(children, lang))
|
|
end
|
|
|
|
def walk({tag, attrs, children, meta}), do: {tag, attrs, walk(children), meta}
|
|
|
|
defp maybe_remove_para(node, false), do: node
|
|
|
|
defp maybe_remove_para({"p", _attrs, children, _meta}, true),
|
|
do: Enum.map(children, &add_trailing_newline/1)
|
|
|
|
defp maybe_remove_para(other, true), do: other
|
|
|
|
defp highlight([text], lang), do: highlight(text, lang)
|
|
|
|
defp highlight(text, lang) do
|
|
|
|
{:ok, highlight} = TreeSitter.highlight_html(text, lang || "plain")
|
|
|
|
[_preamble, rest] = String.split(highlight, "<table>")
|
|
[tbody, _rest] = String.split(rest, "</table>")
|
|
|
|
table = "<table>\n#{tbody}\n</table>"
|
|
|
|
{"code", [], [table], %{verbatim: true}}
|
|
end
|
|
|
|
def floki2earmark(list) when is_list(list) do
|
|
Enum.map(list, &floki2earmark/1)
|
|
end
|
|
|
|
def floki2earmark(text) when is_binary(text), do: text
|
|
|
|
def floki2earmark({tag, attrs, children}), do: {tag, attrs, floki2earmark(children), %{}}
|
|
|
|
defp add_trailing_newline(string) when is_binary(string), do: "#{string}\n"
|
|
defp add_trailing_newline(other), do: other
|
|
|
|
defp host(), do: Application.get_env(:pre_dot_hn, :host)
|
|
end
|