wiki/lib/wiki/content/renderer.ex

76 lines
2.2 KiB
Elixir
Raw Normal View History

2020-07-30 02:21:25 +00:00
defmodule Wiki.Content.Renderer do
alias Wiki.Repo
alias Wiki.Content.Page
alias WikiWeb.Router.Helpers, as: Routes
alias WikiWeb.Endpoint
import Ecto.Query
2020-08-02 20:12:38 +00:00
@spec render(String.t(), integer()) :: String.t()
2020-08-01 23:37:16 +00:00
def render(content, user_id) do
2020-08-02 20:12:38 +00:00
{content, _linked_pages} = replace_page_links(content, user_id)
2020-08-01 23:37:16 +00:00
2020-08-02 20:12:38 +00:00
content
|> render_markdown()
end
@spec get_linked_pages(page :: Page.t()) :: [integer()]
def get_linked_pages(%Page{content: content, user_id: user_id}) do
{_content, linked_pages} = replace_page_links(content, user_id)
Enum.uniq(linked_pages)
2020-07-30 02:21:25 +00:00
end
@page_link_regex ~r/\[\[.*?\]\]/
2020-08-01 23:37:16 +00:00
@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)
2020-07-30 02:21:25 +00:00
title = String.slice(match, 2..-3)
lower_title = String.downcase(title)
2020-08-01 23:37:16 +00:00
str_before = :erlang.binary_part(str, 0, start)
str_after = :erlang.binary_part(str, start + length, byte_size(str) - start - length)
{path, acc} =
2020-07-30 02:21:25 +00:00
from(p in Page)
2020-08-01 23:37:16 +00:00
|> where([p], p.user_id == ^user_id)
2020-07-30 02:21:25 +00:00
|> where([p], fragment("lower(?)", p.title) == ^lower_title)
|> select([p], p.id)
|> Repo.one()
|> case do
nil ->
2020-08-01 23:37:16 +00:00
{
Routes.page_path(Endpoint, :new, title: title),
acc
}
2020-07-30 02:21:25 +00:00
linked_page_id ->
2020-08-01 23:37:16 +00:00
{
Routes.page_path(Endpoint, :show, linked_page_id),
[linked_page_id | acc]
}
2020-07-30 02:21:25 +00:00
end
2020-08-01 23:37:16 +00:00
page_link = "[#{title}](#{path})"
{
str_before <> page_link <> str_after,
acc
}
2020-07-30 02:21:25 +00:00
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