defmodule Temple do defmacro __using__(_) do quote do import unquote(__MODULE__) import Temple.Tags import Temple.Form import Temple.Link end end @doc """ Creates a markup context. All tags must be called inside of a `Temple.htm/1` block. Returns a safe result of the form `{:safe, result}` ## Example ``` team = ["Alice", "Bob", "Carol"] htm do for name <- team do div class: "text-bold" do text name end end end # {:safe, "
Alice
Bob
Carol
"} ``` """ defmacro htm([do: block] = _block) do quote do import Kernel, except: [div: 2] with {:ok, var!(buff, Temple.Tags)} <- Temple.Utils.start_buffer([]) do unquote(block) markup = Temple.Utils.get_buffer(var!(buff, Temple.Tags)) :ok = Temple.Utils.stop_buffer(var!(buff, Temple.Tags)) Temple.Utils.join_and_escape(markup) end end end @doc """ Emits a text node into the markup. ``` htm do div do text "Hello, world!" end end # {:safe, "
Hello, world!
"} ``` """ defmacro text(text) do quote do Temple.Utils.put_buffer( var!(buff, Temple.Tags), unquote(text) |> to_string |> Phoenix.HTML.html_escape() |> Phoenix.HTML.safe_to_string() ) end end @doc """ Emits a Phoenix partial into the markup. ``` htm do html lang: "en" do head do title "MyApp" link rel: "stylesheet", href: Routes.static_path(@conn, "/css/app.css") end body do main role: "main", class: "container" do p get_flash(@conn, :info), class: "alert alert-info", role: "alert" p get_flash(@conn, :error), class: "alert alert-danger", role: "alert" partial render(@view_module, @view_template, assigns) end script type: "text/javascript", src: Routes.static_path(@conn, "/js/app.js") end end end ``` """ defmacro partial(partial) do quote do Temple.Utils.put_buffer( var!(buff, Temple.Tags), unquote(partial) |> Temple.Utils.from_safe() ) end end @doc """ Defines a custom component. Components are the primary way to extract partials and markup helpers. ## Assigns Components accept a keyword list or a map of assigns and can be referenced in the body of the component by a module attribute of the same name. This works exactly the same as EEx templates. ## Children If a block is passed to the component, it can be referenced by a special assign called `@children`. ## Example ``` defcomponent :flex do div id: @id, class: "flex" do @children end end htm do flex id: "my-flex" do div "Item 1" div "Item 2" div "Item 3" end end # {:safe, "
#
Item 1
#
Item 2
#
Item 3
#
"} ``` """ defmacro defcomponent(name, [do: _] = block) do quote do defmacro unquote(name)() do outer = unquote(Macro.escape(block)) quote do _ = unquote(outer) end end defmacro unquote(name)(props_or_block) defmacro unquote(name)([{:do, inner}]) do name = unquote(name) quote do unquote(name)([], unquote(inner)) end end defmacro unquote(name)(props) do name = unquote(name) quote do unquote(name)(unquote(props), nil) end end defmacro unquote(name)(props, inner) do outer = unquote(Macro.escape(block)) |> Macro.prewalk(&Temple.Utils.insert_props(&1, props, inner)) name = unquote(name) quote do _ = unquote(outer) end end end end end