From 8715db806a35bf92587d36d43e8fb3edf9f84750 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Wed, 29 Jul 2020 22:21:25 -0400 Subject: [PATCH] Markdown --- lib/wiki/accounts/user.ex | 8 ++++ lib/wiki/content.ex | 4 +- lib/wiki/content/page.ex | 36 ++++++++++---- lib/wiki/content/renderer.ex | 47 +++++++++++++++++++ lib/wiki_web/controllers/page_controller.ex | 7 +-- lib/wiki_web/templates/page/show.html.eex | 2 +- .../20200726020001_create_pages.exs | 1 + 7 files changed, 90 insertions(+), 15 deletions(-) create mode 100644 lib/wiki/content/renderer.ex diff --git a/lib/wiki/accounts/user.ex b/lib/wiki/accounts/user.ex index 0872f0a..d422eca 100644 --- a/lib/wiki/accounts/user.ex +++ b/lib/wiki/accounts/user.ex @@ -2,6 +2,14 @@ defmodule Wiki.Accounts.User do use Ecto.Schema import Ecto.Changeset + @type t() :: %__MODULE__{ + email: String.t(), + password: String.t() | nil, + hashed_password: String.t(), + confirmed_at: NaiveDateTime.t(), + content_encryption_key_salt: String.t() + } + @derive {Inspect, except: [:password]} schema "users" do field :email, :string diff --git a/lib/wiki/content.ex b/lib/wiki/content.ex index 84bb535..5ece37d 100644 --- a/lib/wiki/content.ex +++ b/lib/wiki/content.ex @@ -100,7 +100,7 @@ defmodule Wiki.Content do %Ecto.Changeset{data: %Page{}} """ - def change_page(%Page{} = page, attrs \\ %{}) do - Page.changeset(page, attrs) + def change_page(%Page{} = page, attrs \\ %{}, options \\ []) do + Page.changeset(page, attrs, options) end end diff --git a/lib/wiki/content/page.ex b/lib/wiki/content/page.ex index 7f5cc34..800fa75 100644 --- a/lib/wiki/content/page.ex +++ b/lib/wiki/content/page.ex @@ -2,12 +2,24 @@ defmodule Wiki.Content.Page do use Ecto.Schema import Ecto.Changeset + @type t() :: %__MODULE__{ + encrypted_content: binary(), + encrypted_content_iv: binary(), + encrypted_content_tag: binary(), + content: String.t() | nil, + content_encryption_key: String.t() | nil, + html: String.t() | nil, + title: String.t(), + user: Wiki.Accounts.User.t() + } + schema "pages" do field :encrypted_content, :binary field :encrypted_content_iv, :binary field :encrypted_content_tag, :binary field :content, :string, virtual: true field :content_encryption_key, :string, virtual: true + field :html, :string, virtual: true field :title, :string belongs_to :user, Wiki.Accounts.User @@ -16,15 +28,21 @@ defmodule Wiki.Content.Page do end @doc false - def changeset(page, attrs) do - page - |> cast(attrs, [ - :title, - :content, - :content_encryption_key, - :user_id - ]) - |> encrypt_changeset() + def changeset(page, attrs, options \\ []) do + changeset = + page + |> cast(attrs, [ + :title, + :content, + :content_encryption_key, + :user_id + ]) + + if Keyword.get(options, :encrypt, true) do + encrypt_changeset(changeset) + else + changeset + end |> validate_required([ :title, :encrypted_content, diff --git a/lib/wiki/content/renderer.ex b/lib/wiki/content/renderer.ex new file mode 100644 index 0000000..0cb5465 --- /dev/null +++ b/lib/wiki/content/renderer.ex @@ -0,0 +1,47 @@ +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(Page.t()) :: String.t() + def render(page) do + page.content + |> replace_page_links(page) + |> render_markdown() + end + + @page_link_regex ~r/\[\[.*?\]\]/ + + @spec replace_page_links(content :: String.t(), root_page :: Page.t()) :: String.t() + defp replace_page_links(content, root_page) do + String.replace(content, @page_link_regex, fn match -> + title = String.slice(match, 2..-3) + lower_title = String.downcase(title) + + path = + from(p in Page) + |> where([p], p.user_id == ^root_page.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) + + linked_page_id -> + Routes.page_path(Endpoint, :show, linked_page_id) + end + + # convert link to markdown, because [[title]] -> link transformation happens before markdown parsing + "[#{title}](#{path})" + 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 diff --git a/lib/wiki_web/controllers/page_controller.ex b/lib/wiki_web/controllers/page_controller.ex index 3c79e05..bb99c25 100644 --- a/lib/wiki_web/controllers/page_controller.ex +++ b/lib/wiki_web/controllers/page_controller.ex @@ -34,8 +34,8 @@ defmodule WikiWeb.PageController do render(conn, "index.html", pages: pages) end - def new(conn, _params) do - changeset = Content.change_page(%Page{}) + def new(conn, params) do + changeset = Content.change_page(%Page{}, params, encrypt: false) render(conn, "new.html", changeset: changeset) end @@ -59,7 +59,8 @@ defmodule WikiWeb.PageController do end def show(conn, _params) do - render(conn, "show.html", page: conn.assigns.page) + rendered_content = Content.Renderer.render(conn.assigns.page) + render(conn, "show.html", page: conn.assigns.page, content: rendered_content) end def edit(conn, _params) do diff --git a/lib/wiki_web/templates/page/show.html.eex b/lib/wiki_web/templates/page/show.html.eex index 552dd4f..c959674 100644 --- a/lib/wiki_web/templates/page/show.html.eex +++ b/lib/wiki_web/templates/page/show.html.eex @@ -9,7 +9,7 @@
  • Content: - <%= @page.content %> + <%= raw(@content) %>
  • diff --git a/priv/repo/migrations/20200726020001_create_pages.exs b/priv/repo/migrations/20200726020001_create_pages.exs index a79bce0..ea1e1f9 100644 --- a/priv/repo/migrations/20200726020001_create_pages.exs +++ b/priv/repo/migrations/20200726020001_create_pages.exs @@ -13,5 +13,6 @@ defmodule Wiki.Repo.Migrations.CreatePages do end create index(:pages, [:user_id]) + create index(:pages, ["(lower(title))"], name: :pages_lowercase_title_index) end end