72 lines
2.0 KiB
Elixir
72 lines
2.0 KiB
Elixir
defmodule Wiki.Content.Renderer do
|
|
alias Wiki.Repo
|
|
alias Wiki.Content.Page
|
|
alias WikiWeb.Router.Helpers, as: Routes
|
|
alias WikiWeb.Endpoint
|
|
import Ecto.Query
|
|
|
|
@spec render(String.t(), integer()) :: {String.t(), [integer()]}
|
|
def render(content, user_id) do
|
|
{content, linked_pages} = replace_page_links(content, user_id)
|
|
|
|
{
|
|
render_markdown(content),
|
|
Enum.uniq(linked_pages)
|
|
}
|
|
end
|
|
|
|
@page_link_regex ~r/\[\[.*?\]\]/
|
|
|
|
@spec replace_page_links(content :: String.t(), user_id :: integer()) ::
|
|
{String.t(), [integer()]}
|
|
defp replace_page_links(content, user_id) do
|
|
results = Regex.scan(@page_link_regex, content, return: :index)
|
|
|
|
results
|
|
|> Enum.reverse()
|
|
|> Enum.reduce({content, []}, fn [{start, length}], {str, acc} ->
|
|
# can't use String.slice here, because the indicies produced by Regex.scan are character indices, and slice operates on graphemes
|
|
match = :erlang.binary_part(str, start, length)
|
|
title = String.slice(match, 2..-3)
|
|
lower_title = String.downcase(title)
|
|
|
|
str_before = :erlang.binary_part(str, 0, start)
|
|
|
|
str_after = :erlang.binary_part(str, start + length, byte_size(str) - start - length)
|
|
|
|
{path, acc} =
|
|
from(p in Page)
|
|
|> where([p], p.user_id == ^user_id)
|
|
|> where([p], fragment("lower(?)", p.title) == ^lower_title)
|
|
|> select([p], p.id)
|
|
|> Repo.one()
|
|
|> case do
|
|
nil ->
|
|
{
|
|
Routes.page_path(Endpoint, :new, title: title),
|
|
acc
|
|
}
|
|
|
|
linked_page_id ->
|
|
{
|
|
Routes.page_path(Endpoint, :show, linked_page_id),
|
|
[linked_page_id | acc]
|
|
}
|
|
end
|
|
|
|
page_link = "[#{title}](#{path})"
|
|
|
|
{
|
|
str_before <> page_link <> str_after,
|
|
acc
|
|
}
|
|
end)
|
|
end
|
|
|
|
@spec render_markdown(content :: String.t()) :: String.t()
|
|
defp render_markdown(content) do
|
|
{:ok, html, _errors} = Earmark.as_html(content)
|
|
html
|
|
end
|
|
end
|