defmodule Pow.Phoenix.Template do
@moduledoc """
Module that can builds templates for Phoenix using EEx with
`Phoenix.HTML.Engine`.
## Example
defmodule MyApp.ResourceTemplate do
use Pow.Phoenix.Template
template :new, :html, "<%= content_tag(:span, "Template") %>"
end
MyApp.ResourceTemplate.new(assigns)
"""
@doc false
defmacro __using__(_opts) do
quote do
import unquote(__MODULE__)
unquote do
# Credo will complain about unless statement but we want this first
# credo:disable-for-next-line
unless Pow.dependency_vsn_match?(:phoenix, "< 1.7.0") do
quote do
use Phoenix.Component
import Pow.Phoenix.HTML.CoreComponents
alias Phoenix.LiveView.JS
end
else
# TODO: Remove when Phoenix 1.7 is required
quote do
import Pow.Phoenix.HTML.ErrorHelpers, only: [error_tag: 2]
import Phoenix.HTML.{Form, Link}
end
end
end
# TODO: Remove when Phoenix 1.7 is required
unquote do
if Code.ensure_loaded?(Phoenix.View) do
quote do
@after_compile {unquote(__MODULE__), :__after_compile_phoenix_view__}
end
end
end
unquote do
# TODO: Remove when Phoenix 1.7 is required
unless Code.ensure_loaded?(Phoenix.VerifiedRoutes) do
quote do
alias Pow.Phoenix.Router.Helpers, as: Routes
end
end
end
end
end
# TODO: Remove when Phoenix 1.7 is required
@doc false
def __after_compile_phoenix_view__(env, _bytecode) do
if Code.ensure_loaded?(Phoenix.View) do
view_module =
env.module
|> Phoenix.Naming.unsuffix("HTML")
|> Kernel.<>("View")
|> String.to_atom()
Module.create(
view_module,
for {name, 1} <- env.module.__info__(:functions) do
quote do
def render(unquote("#{name}.html"), assigns) do
apply(unquote(env.module), unquote(name), [assigns])
end
end
end,
Macro.Env.location(__ENV__))
end
end
@doc """
Generates HTML template functions.
This macro that will compile a phoenix template from the provided binary, and
add the compiled version to a `:action/2` function. The `html/1` function
outputs the binary.
"""
@spec template(atom(), atom(), binary() | {atom(), any()}) :: Macro.t()
defmacro template(action, :html, content) do
content = "<% import #{__MODULE__}, only: [__inline_route__: 2, __user_id_field__: 2] %>#{content}"
content =
# Credo will complain about unless statement but we want this first
# credo:disable-for-next-line
unless Pow.dependency_vsn_match?(:phoenix, "< 1.7.0") do
content
else
# TODO: Remove when Phoenix 1.7 required
"<% import #{Pow.Phoenix.HTML.FormTemplate}, only: [render_form: 1, render_form: 2] %>#{content}"
end
content =
# TODO: Remove when Phoenix 1.7 required and fallback templates removed
# Credo will complain about unless statement but we want this first
# credo:disable-for-next-line
unless Pow.dependency_vsn_match?(:phoenix, "< 1.7.0") do
String.replace(content, "<%= render_form", "<%% render_form")
else
content
end
expr =
EEx.eval_string(
content,
[],
file: __CALLER__.file,
line: __CALLER__.line + 1,
caller: __CALLER__)
opts =
# Credo will complain about unless statement but we want this first
# credo:disable-for-next-line
unless Pow.dependency_vsn_match?(:phoenix, "< 1.7.0") do
[
engine: Phoenix.LiveView.TagEngine,
file: __CALLER__.file,
line: __CALLER__.line + 1,
caller: %{__CALLER__ | function: {:template, 3}},
source: expr,
tag_handler: Phoenix.LiveView.HTMLEngine]
else
# TODO: Remove when Phoenix 1.7 required
[
engine: Phoenix.HTML.Engine,
file: __CALLER__.file,
line: __CALLER__.line + 1,
caller: __CALLER__,
source: expr]
end
quoted = EEx.compile_string(expr, opts)
quote do
def unquote(action)(var!(assigns)) do
_ = var!(assigns)
unquote(quoted)
end
def html(unquote(action)), do: unquote(expr)
end
end
@doc false
if Code.ensure_loaded?(Phoenix.VerifiedRoutes) do
def __inline_route__(plug, plug_opts) do
"Pow.Phoenix.Routes.path_for(@conn, #{inspect plug}, #{inspect plug_opts})"
end
else
# TODO: Remove when Phoenix 1.7 is required
def __inline_route__(plug, plug_opts) do
"Routes.#{Pow.Phoenix.Controller.route_helper(plug)}_path(@conn, #{inspect plug_opts}) %>"
end
end
@doc false
def __user_id_field__(type, :key) do
"Pow.Ecto.Schema.user_id_field(#{type})"
end
def __user_id_field__(type, :type) do
"Pow.Ecto.Schema.user_id_field(#{type}) == :email && \"email\" || \"text\""
end
def __user_id_field__(type, :label) do
"Phoenix.Naming.humanize(Pow.Ecto.Schema.user_id_field(#{type}))"
end
end