diff --git a/README.md b/README.md index c4930c5..cc9eccd 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,22 @@ You can see Swoosh.Gallery in action with the Phoenix app included on `./sample` 1. Run `mix do deps.get, phx.server` 2. Go to `http://localhost:4000/dev/emails` + + +### Static gallery + +You can also generate static HTML files for you Gallery. This is useful when you want to expose the gallery without the need of a server. + +```bash +mix swoosh.gallery.html --gallery Sample.Gallery --path="./_build/gallery" + +open _build/gallery/index.html_ +``` + + ## Contributing 1. Download the project. 2. Run `mix do deps.get, tailwind.install` 3. Make some changes. -4. If you need add new tailwind styles, run `mix tailwind default`. \ No newline at end of file +4. If you need add new tailwind styles, run `mix tailwind default`. diff --git a/lib/mix/tasks/swoosh.gallery.html.ex b/lib/mix/tasks/swoosh.gallery.html.ex index 64e76a3..a997bb5 100644 --- a/lib/mix/tasks/swoosh.gallery.html.ex +++ b/lib/mix/tasks/swoosh.gallery.html.ex @@ -60,7 +60,7 @@ defmodule Mix.Tasks.Swoosh.Gallery.Html do |> Module.concat() |> Code.ensure_compiled!() |> tap(fn mod -> - unless function_exported?(mod, :previews, 0) do + unless function_exported?(mod, :get, 0) do Mix.raise(""" The module #{inspect(mod)} is not a valid gallery. Make sure it uses Swoosh.Gallery: @@ -72,13 +72,14 @@ defmodule Mix.Tasks.Swoosh.Gallery.Html do """) end end) - |> tap(&ensure_required_functions!(&1.previews)) + |> then(& &1.get()) + |> tap(&ensure_required_functions!(&1)) else Mix.raise("No gallery available. Please pass a gallery with the --gallery option") end end - defp ensure_required_functions!(previews) when is_list(previews) do + defp ensure_required_functions!(%{previews: previews}) when is_list(previews) do Enum.each(previews, fn %{email_mfa: {module, _fun, _args}} -> ensure_required_functions!(module) end) diff --git a/lib/swoosh/gallery.ex b/lib/swoosh/gallery.ex index 9247721..8434409 100644 --- a/lib/swoosh/gallery.ex +++ b/lib/swoosh/gallery.ex @@ -92,7 +92,7 @@ defmodule Swoosh.Gallery do @group_path nil def init(opts) do - Keyword.put(opts, :gallery, __MODULE__) + Keyword.put(opts, :gallery, __MODULE__.get()) end def call(conn, opts) do @@ -106,9 +106,10 @@ defmodule Swoosh.Gallery do defmacro __before_compile__(_env) do quote do @doc false - def previews, do: @previews - - def groups, do: @groups + def get do + previews = eval_details(@previews) + %{previews: previews, groups: @groups} + end end end @@ -131,14 +132,14 @@ defmodule Swoosh.Gallery do defmacro preview(path, module) do path = validate_path(path) module = Macro.expand(module, __ENV__) - preview_details = Macro.escape(eval_preview_details(module)) + validate_preview_details!(module) quote do @previews %{ group: @group_path, path: build_preview_path(@group_path, unquote(path)), email_mfa: {unquote(module), :preview, []}, - preview_details: unquote(preview_details) + details_mfa: {unquote(module), :preview_details, []} } end end @@ -187,18 +188,47 @@ defmodule Swoosh.Gallery do end end - # Evaluates a preview. It loads the results of email_mfa into the email property. + # Evaluates a preview. It loads the results of email_mfa and details_mfa into the email + # and preview_details properties respectively. @doc false - @spec eval_preview(%{:email_mfa => {module(), atom(), list()}}) :: map() + @spec eval_preview(%{ + :email_mfa => {module(), atom(), list()}, + :details_mfa => {module(), atom(), list()} + }) :: map() def eval_preview(%{email: _email} = preview), do: preview - def eval_preview(%{email_mfa: {module, fun, opts}} = preview) do + def eval_preview(preview) do + preview + |> eval_email() + |> eval_details() + end + + defp eval_email(%{email_mfa: {module, fun, opts}} = preview) do Map.put(preview, :email, apply(module, fun, opts)) end + # Evaluates preview details. It loads the results of details_mfa into the + # preview_details property. + @doc false + @spec eval_details( + %{:details_mfa => {module(), atom(), list()}} + | list(%{:details_mfa => {module(), atom(), list()}}) + ) :: map() + def eval_details(%{preview_details: _details} = preview), do: preview + + def eval_details(%{details_mfa: {module, fun, opts}} = preview) do + Map.put(preview, :preview_details, validate_preview_details!(module, fun, opts)) + end + + def eval_details(previews) when is_list(previews) do + Enum.map(previews, fn %{details_mfa: _mfa} = preview -> + eval_details(preview) + end) + end + # Evaluates a preview and reads the attachment at a given index position. @doc false - @spec read_email_attachment_at(%{email_mfa: {atom, atom, list}}, integer()) :: + @spec read_email_attachment_at(%{email_mfa: {module, atom, list}}, integer()) :: {:error, :invalid_attachment | :not_found} | {:ok, %{content_type: String.t(), data: any}} def read_email_attachment_at(preview, index) do @@ -223,9 +253,9 @@ defmodule Swoosh.Gallery do end end - defp eval_preview_details(module) do + defp validate_preview_details!(module, fun \\ :preview_details, opts \\ []) do module - |> apply(:preview_details, []) + |> apply(fun, opts) |> Keyword.validate!([:title, :description, tags: []]) |> Map.new() |> tap(&ensure_title!/1) diff --git a/lib/swoosh/gallery/plug.ex b/lib/swoosh/gallery/plug.ex index ab737b6..3eed33d 100644 --- a/lib/swoosh/gallery/plug.ex +++ b/lib/swoosh/gallery/plug.ex @@ -1,7 +1,7 @@ defmodule Swoosh.Gallery.Plug do @moduledoc """ Plug to mount a `Swoosh.Gallery` into an existing Phoenix/Plug application. Gallery themselves - will forward `init/1` and `call/2` to this dmoule, so users don't need to use it directly. + will forward `init/1` and `call/2` to this module, so users don't need to use it directly. ## Examples diff --git a/test/mix/tasks/swoosh.gallery.html_test.exs b/test/mix/tasks/swoosh.gallery.html_test.exs index b8c4421..cc44dbc 100644 --- a/test/mix/tasks/swoosh.gallery.html_test.exs +++ b/test/mix/tasks/swoosh.gallery.html_test.exs @@ -20,14 +20,14 @@ defmodule Mix.Tasks.Swoosh.Gallery.HtmlTest do assert File.dir?(tmp_dir) assert sorted_ls!(tmp_dir) == [ + "auth.reset_password", + "auth.reset_password.html", "index.html", - "reset_password", - "reset_password.html", "welcome", "welcome.html" ] - assert sorted_ls!("#{tmp_dir}/reset_password") == ["preview.html"] + assert sorted_ls!("#{tmp_dir}/auth.reset_password") == ["preview.html"] assert sorted_ls!("#{tmp_dir}/welcome") == ["attachments", "preview.html"] assert sorted_ls!("#{tmp_dir}/welcome/attachments") == ["0"] assert sorted_ls!("#{tmp_dir}/welcome/attachments/0") == ["my_file.txt"] @@ -44,7 +44,7 @@ defmodule Mix.Tasks.Swoosh.Gallery.HtmlTest do test "has links to the previews", %{tmp_dir: tmp_dir} do run_task(Support.Gallery, tmp_dir) contents = File.read!("#{tmp_dir}/index.html") - assert contents =~ "a href=\"./reset_password.html\"" + assert contents =~ "a href=\"./auth.reset_password.html\"" assert contents =~ "a href=\"./welcome.html\"" end @@ -58,7 +58,7 @@ defmodule Mix.Tasks.Swoosh.Gallery.HtmlTest do test "accessing a preview shows the email as text", %{tmp_dir: tmp_dir} do run_task(Support.Gallery, tmp_dir) - contents = File.read!("#{tmp_dir}/reset_password.html") + contents = File.read!("#{tmp_dir}/auth.reset_password.html") assert contents =~ "Reset Password" assert contents =~ "Sends instructions on how to reset password" assert contents =~ "passwords: yes" @@ -67,7 +67,7 @@ defmodule Mix.Tasks.Swoosh.Gallery.HtmlTest do test "accessing a preview.html shows the email as html", %{tmp_dir: tmp_dir} do run_task(Support.Gallery, tmp_dir) - contents = File.read!("#{tmp_dir}/reset_password/preview.html") + contents = File.read!("#{tmp_dir}/auth.reset_password/preview.html") assert contents == "Please, reset your password here." diff --git a/test/support/gallery.ex b/test/support/gallery.ex index 4d6e5f3..c610af9 100644 --- a/test/support/gallery.ex +++ b/test/support/gallery.ex @@ -2,5 +2,8 @@ defmodule Support.Gallery do use Swoosh.Gallery preview("/welcome", Support.Emails.WelcomeEmail) - preview("/reset_password", Support.Emails.ResetPasswordEmail) + + group "/auth", title: "Auth" do + preview("/reset_password", Support.Emails.ResetPasswordEmail) + end end diff --git a/test/swoosh/gallery_test.exs b/test/swoosh/gallery_test.exs index 5d7cdb3..6d31083 100644 --- a/test/swoosh/gallery_test.exs +++ b/test/swoosh/gallery_test.exs @@ -8,30 +8,33 @@ defmodule Swoosh.GalleryTest do describe "previews/0" do test "returns the list of previews" do - assert previews = Support.Gallery.previews() + assert previews = Support.Gallery.get() - assert [ - %{ - preview_details: %{ - description: "Sends instructions on how to reset password", - tags: [passwords: "yes"], - title: "Reset Password" + assert %{ + previews: [ + %{ + preview_details: %{ + description: "Sends instructions on how to reset password", + tags: [passwords: "yes"], + title: "Reset Password" + }, + path: "auth.reset_password", + email_mfa: {Support.Emails.ResetPasswordEmail, :preview, []}, + group: "auth" }, - path: "reset_password", - email_mfa: {Support.Emails.ResetPasswordEmail, :preview, []}, - group: nil - }, - %{ - preview_details: %{ - description: "Sends a warm welcome to the user", - tags: [attachments: "yes"], - title: "Welcome" - }, - path: "welcome", - email_mfa: {Support.Emails.WelcomeEmail, :preview, []}, - group: nil - } - ] == previews + %{ + preview_details: %{ + description: "Sends a warm welcome to the user", + tags: [attachments: "yes"], + title: "Welcome" + }, + path: "welcome", + email_mfa: {Support.Emails.WelcomeEmail, :preview, []}, + group: nil + } + ], + groups: [%{path: "auth", title: "Auth"}] + } = previews end end @@ -46,7 +49,7 @@ defmodule Swoosh.GalleryTest do test "has links to the previews" do response = Router.call(conn(:get, "/gallery"), []) assert response.status == 200 - assert response.resp_body =~ "a href=\"/gallery/reset_password\"" + assert response.resp_body =~ "a href=\"/gallery/auth.reset_password\"" assert response.resp_body =~ "a href=\"/gallery/welcome\"" end @@ -59,7 +62,7 @@ defmodule Swoosh.GalleryTest do end test "accessing a preview shows the email as text" do - response = Router.call(conn(:get, "/gallery/reset_password"), []) + response = Router.call(conn(:get, "/gallery/auth.reset_password"), []) assert response.status == 200 assert response.resp_body =~ "Reset Password" assert response.resp_body =~ "Sends instructions on how to reset password" @@ -68,7 +71,7 @@ defmodule Swoosh.GalleryTest do end test "accessing a preview.html shows the email as html" do - response = Router.call(conn(:get, "/gallery/reset_password/preview.html"), []) + response = Router.call(conn(:get, "/gallery/auth.reset_password/preview.html"), []) assert response.status == 200 assert response.resp_body ==