parent
43bd75056f
commit
33c95186fb
|
@ -16,7 +16,7 @@ locals_without_parens = ~w[
|
||||||
details summary menuitem menu
|
details summary menuitem menu
|
||||||
meta link base
|
meta link base
|
||||||
area br col embed hr img input keygen param source track wbr
|
area br col embed hr img input keygen param source track wbr
|
||||||
text partial
|
txt partial
|
||||||
|
|
||||||
animate animateMotion animateTransform circle clipPath
|
animate animateMotion animateTransform circle clipPath
|
||||||
color-profile defs desc discard ellipse feBlend
|
color-profile defs desc discard ellipse feBlend
|
||||||
|
@ -24,7 +24,7 @@ locals_without_parens = ~w[
|
||||||
feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset
|
feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset
|
||||||
fePointLight feSpecularLighting feSpotLight feTile feTurbulence filter foreignObject g hatch hatchpath image line linearGradient
|
fePointLight feSpecularLighting feSpotLight feTile feTurbulence filter foreignObject g hatch hatchpath image line linearGradient
|
||||||
marker mask mesh meshgradient meshpatch meshrow metadata mpath path pattern polygon
|
marker mask mesh meshgradient meshpatch meshrow metadata mpath path pattern polygon
|
||||||
polyline radialGradient rect set solidcolor stop svg switch symbol text_
|
polyline radialGradient rect set solidcolor stop svg switch symbol text
|
||||||
textPath tspan unknown use view
|
textPath tspan unknown use view
|
||||||
|
|
||||||
form_for inputs_for
|
form_for inputs_for
|
||||||
|
|
|
@ -24,9 +24,49 @@ jobs:
|
||||||
id: cache
|
id: cache
|
||||||
with:
|
with:
|
||||||
path: deps
|
path: deps
|
||||||
key: ${{ runner.os }}-${{ matrix.elixir }}-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
|
key: ${{ runner.os }}-test-${{ matrix.elixir }}-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
|
||||||
restore-keys: |
|
restore-keys:
|
||||||
${{ runner.os }}-mix-
|
- name: Install Dependencies
|
||||||
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
|
run: mix deps.get
|
||||||
|
|
||||||
|
- name: Run Tests
|
||||||
|
run: mix test
|
||||||
|
|
||||||
|
integration_tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Integration Test (${{matrix.elixir}}/${{matrix.otp}})
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: "./integration_test/temple_demo"
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
otp: [21.x]
|
||||||
|
elixir: [1.7.x, 1.8.x, 1.9.x, 1.10.x]
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:12
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: temple_demo_test
|
||||||
|
ports: ['5432:5432']
|
||||||
|
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
||||||
|
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-elixir@v1.2.0
|
||||||
|
with:
|
||||||
|
otp-version: ${{matrix.otp}}
|
||||||
|
elixir-version: ${{matrix.elixir}}
|
||||||
|
- uses: actions/cache@v1
|
||||||
|
id: cache
|
||||||
|
with:
|
||||||
|
path: deps
|
||||||
|
key: ${{ runner.os }}-integration-test-${{ matrix.elixir }}-${{ hashFiles(format('{0}{1}', github.workspace, '/integration_test/temple_demo/mix.lock')) }}
|
||||||
|
|
||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
|
@ -34,6 +74,14 @@ jobs:
|
||||||
|
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: mix test
|
run: mix test
|
||||||
|
env:
|
||||||
|
MIX_ENV: test
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v2
|
||||||
|
if: failure()
|
||||||
|
with:
|
||||||
|
name: screenshots
|
||||||
|
path: screenshots/
|
||||||
|
|
||||||
formatter:
|
formatter:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
# The directory Mix downloads your dependencies sources to.
|
# The directory Mix downloads your dependencies sources to.
|
||||||
/deps/
|
/deps/
|
||||||
|
/integration_test/temple_demo/deps/
|
||||||
|
|
||||||
# Where third-party dependencies like ExDoc output generated docs.
|
# Where third-party dependencies like ExDoc output generated docs.
|
||||||
/doc/
|
/doc/
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
elixir 1.10.2
|
elixir 1.10.2
|
||||||
erlang 22.3.1
|
erlang 23.0.1
|
||||||
|
|
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -2,6 +2,17 @@
|
||||||
|
|
||||||
## Master
|
## Master
|
||||||
|
|
||||||
|
## 0.6.0-alpha.0
|
||||||
|
|
||||||
|
### Breaking!
|
||||||
|
|
||||||
|
This version is the start of a complete rewrite of Temple.
|
||||||
|
|
||||||
|
- Compiles to EEx at build time.
|
||||||
|
- Compatible with `Phoenix.LiveView`
|
||||||
|
- All modules other than `Temple` are removed
|
||||||
|
- `mix temple.convert` Mix task removed
|
||||||
|
|
||||||
## 0.5.0
|
## 0.5.0
|
||||||
|
|
||||||
- Introduce `@assigns` assign
|
- Introduce `@assigns` assign
|
||||||
|
|
99
README.md
99
README.md
|
@ -1,12 +1,6 @@
|
||||||
# ![](temple.png)
|
# ![](temple.png)
|
||||||
|
|
||||||
> Temple is now undergoing a rewrite. The goal is to compile to EEx at compile time, so that it can then be fed straight into the Phoenix HTML and LiveView engines. This way, Temple becomes compatible with LiveView as well as gaining all the same optimizations as normal Phoenix templates.
|
> You are looking at the README for the master branch. The README for the latest stable release is located [here](https://github.com/mhanberg/temple/tree/v0.5.0).
|
||||||
>
|
|
||||||
> There is no guarantee that the rewrite will maintain the current feature set of Temple, mainly the Component API.
|
|
||||||
>
|
|
||||||
> To follow along, please checkout out the [rewrite](https://github.com/mhanberg/temple/tree/rewrite) branch. The code will likely be in a "spike" state until further notice, so don't let the WIP commits scare you off 😄
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
[![Actions Status](https://github.com/mhanberg/temple/workflows/CI/badge.svg)](https://github.com/mhanberg/temple/actions)
|
[![Actions Status](https://github.com/mhanberg/temple/workflows/CI/badge.svg)](https://github.com/mhanberg/temple/actions)
|
||||||
[![Hex.pm](https://img.shields.io/hexpm/v/temple.svg)](https://hex.pm/packages/temple)
|
[![Hex.pm](https://img.shields.io/hexpm/v/temple.svg)](https://hex.pm/packages/temple)
|
||||||
|
@ -14,7 +8,7 @@
|
||||||
|
|
||||||
Temple is a DSL for writing HTML using Elixir.
|
Temple is a DSL for writing HTML using Elixir.
|
||||||
|
|
||||||
You're probably here because you want to use Temple to write Phoenix templates, which is why Temple includes a [Phoenix template engine](#phoenix-templates) and Temple-compatible [Phoenix form helpers](#phoenixhtml).
|
You're probably here because you want to use Temple to write Phoenix templates, which is why Temple includes a [Phoenix template engine](#phoenix-templates).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -22,13 +16,13 @@ Add `temple` to your list of dependencies in `mix.exs`:
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
def deps do
|
def deps do
|
||||||
[{:temple, "~> 0.5.0"}]
|
[{:temple, "~> 0.6.0-alpha.0"}]
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Using Temple is a as simple as using the DSL inside of an `temple/1` block. This returns a safe result of the form `{:safe, html_string}`.
|
Using Temple is a as simple as using the DSL inside of an `temple/1` block. This returns an EEx string at compile time.
|
||||||
|
|
||||||
See the [documentation](https://hexdocs.pm/temple/Temple.Html.html) for more details.
|
See the [documentation](https://hexdocs.pm/temple/Temple.Html.html) for more details.
|
||||||
|
|
||||||
|
@ -36,7 +30,7 @@ See the [documentation](https://hexdocs.pm/temple/Temple.Html.html) for more det
|
||||||
use Temple
|
use Temple
|
||||||
|
|
||||||
temple do
|
temple do
|
||||||
h2 "todos"
|
h2 do: "todos"
|
||||||
|
|
||||||
ul class: "list" do
|
ul class: "list" do
|
||||||
for item <- @items do
|
for item <- @items do
|
||||||
|
@ -45,12 +39,12 @@ temple do
|
||||||
div class: "bullet hidden"
|
div class: "bullet hidden"
|
||||||
end
|
end
|
||||||
|
|
||||||
div item
|
div do: item
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
script """
|
script do: """
|
||||||
function toggleCheck({currentTarget}) {
|
function toggleCheck({currentTarget}) {
|
||||||
currentTarget.children[0].children[0].classList.toggle("hidden");
|
currentTarget.children[0].children[0].classList.toggle("hidden");
|
||||||
}
|
}
|
||||||
|
@ -62,81 +56,37 @@ temple do
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
### Components
|
|
||||||
|
|
||||||
Temple provides an API for creating custom components that act as custom HTML elements.
|
|
||||||
|
|
||||||
These components can be given `assigns` that are available inside the component definition as module attributes. The contents of a components `do` block are available as a special `@children` assign.
|
|
||||||
|
|
||||||
See the [documentation](https://hexdocs.pm/temple/Temple.html#defcomponent/2) for more details.
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
defcomponent :flex do
|
|
||||||
div id: @id, class: "flex" do
|
|
||||||
@children
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
temple do
|
|
||||||
flex id: "my-flex" do
|
|
||||||
div "Item 1"
|
|
||||||
div "Item 2"
|
|
||||||
div "Item 3"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phoenix.HTML
|
|
||||||
|
|
||||||
Temple provides macros for working with the helpers provided by the [Phoenix.HTML](https://www.github.com/phoenixframework/phoenix_html) package.
|
|
||||||
|
|
||||||
Most of the macros are purely wrappers, while the semantics of some are changed to work with Temple.
|
|
||||||
|
|
||||||
See the [documentation](https://hexdocs.pm/temple/Temple.Form.html#content) for more details.
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
temple do
|
|
||||||
form_for @conn, Routes.some_path(@conn, :create) do
|
|
||||||
text_input form, :name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phoenix templates
|
### Phoenix templates
|
||||||
|
|
||||||
Add the templating engine to your Phoenix configuration.
|
Add the templating engine to your Phoenix configuration.
|
||||||
|
|
||||||
See the [documentation](https://hexdocs.pm/temple/Temple.Engine.html#content) for more details.
|
See the [Temple.Engine](https://hexdocs.pm/temple/Temple.Engine.html#content) and [Temple.LiveEngine](https://hexdocs.pm/temple/Temple.LiveEngine.html#content) for more details.
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
# config.exs
|
# config.exs
|
||||||
config :phoenix, :template_engines, exs: Temple.Engine
|
config :phoenix, :template_engines,
|
||||||
|
exs: Temple.Engine
|
||||||
|
exs: Temple.LiveEngine
|
||||||
|
|
||||||
# config/dev.exs
|
# config/dev.exs
|
||||||
config :your_app, YourAppWeb.Endpoint,
|
config :your_app, YourAppWeb.Endpoint,
|
||||||
live_reload: [
|
live_reload: [
|
||||||
patterns: [
|
patterns: [
|
||||||
~r"lib/your_app_web/templates/.*(exs)$"
|
~r"lib/myapp_web/(live|views)/.*(ex|exs)$",
|
||||||
|
~r"lib/myapp_web/templates/.*(eex|exs)$"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
# your_app_web.ex
|
|
||||||
def view do
|
|
||||||
quote do
|
|
||||||
# ...
|
|
||||||
use Temple # Replaces the call to import Phoenix.HTML
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
# app.html.exs
|
# app.html.exs
|
||||||
|
|
||||||
html lang: "en" do
|
html lang: "en" do
|
||||||
head do
|
head do
|
||||||
meta charset: "utf-8"
|
meta charset: "utf-8"
|
||||||
meta http_equiv: "X-UA-Compatible", content: "IE=edge"
|
meta http_equiv: "X-UA-Compatible", content: "IE=edge"
|
||||||
meta name: "viewport", content: "width=device-width, initial-scale=1.0"
|
meta name: "viewport", content: "width=device-width, initial-scale=1.0"
|
||||||
title "YourApp · Phoenix Framework"
|
title do: "YourApp · Phoenix Framework"
|
||||||
|
|
||||||
link rel: "stylesheet", href: Routes.static_path(@conn, "/css/app.css")
|
link rel: "stylesheet", href: Routes.static_path(@conn, "/css/app.css")
|
||||||
end
|
end
|
||||||
|
@ -158,10 +108,15 @@ html lang: "en" do
|
||||||
end
|
end
|
||||||
|
|
||||||
main role: "main", class: "container" do
|
main role: "main", class: "container" do
|
||||||
p get_flash(@conn, :info), class: "alert alert-info", role: "alert"
|
p class: "alert alert-info", role: "alert" do
|
||||||
p get_flash(@conn, :error), class: "alert alert-danger", role: "alert"
|
get_flash(@conn, :info)
|
||||||
|
end
|
||||||
|
|
||||||
partial render(@view_module, @view_template, assigns)
|
p class: "alert alert-danger", role: "alert" do
|
||||||
|
get_flash(@conn, :error)
|
||||||
|
end
|
||||||
|
|
||||||
|
render @view_module, @view_template, assigns
|
||||||
end
|
end
|
||||||
|
|
||||||
script type: "text/javascript", src: Routes.static_path(@conn, "/js/app.js")
|
script type: "text/javascript", src: Routes.static_path(@conn, "/js/app.js")
|
||||||
|
@ -171,11 +126,13 @@ end
|
||||||
|
|
||||||
### Tasks
|
### Tasks
|
||||||
|
|
||||||
#### temple.convert
|
#### temple.gen.layout
|
||||||
|
|
||||||
This task can be used to convert plain HTML and SVG into Temple syntax. Input is taken from stdin or from a file and the output is sent to stdout.
|
Generates the app layout.
|
||||||
|
|
||||||
`cat index.html | mix temple.convert > index.html.exs`
|
#### temple.gen.html
|
||||||
|
|
||||||
|
Generates the templates for a resource.
|
||||||
|
|
||||||
### Formatter
|
### Formatter
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
(cd integration_test/temple_demo && mix test)
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
mix test
|
||||||
|
|
||||||
|
(cd integration_test/temple_demo && mix test)
|
|
@ -0,0 +1,5 @@
|
||||||
|
[
|
||||||
|
import_deps: [:ecto, :phoenix, :temple],
|
||||||
|
inputs: ["*.{ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{ex,exs}"],
|
||||||
|
subdirectories: ["priv/*/migrations"]
|
||||||
|
]
|
|
@ -0,0 +1,26 @@
|
||||||
|
# The directory Mix will write compiled artifacts to.
|
||||||
|
/_build/
|
||||||
|
|
||||||
|
# If you run "mix test --cover", coverage assets end up here.
|
||||||
|
/cover/
|
||||||
|
|
||||||
|
# The directory Mix downloads your dependencies sources to.
|
||||||
|
/deps/
|
||||||
|
|
||||||
|
# Where 3rd-party dependencies like ExDoc output generated docs.
|
||||||
|
/doc/
|
||||||
|
|
||||||
|
# Ignore .fetch files in case you like to edit your project deps locally.
|
||||||
|
/.fetch
|
||||||
|
|
||||||
|
# If the VM crashes, it generates a dump, let's ignore it too.
|
||||||
|
erl_crash.dump
|
||||||
|
|
||||||
|
# Also ignore archive artifacts (built via "mix archive.build").
|
||||||
|
*.ez
|
||||||
|
|
||||||
|
# Ignore package tarball (built via "mix hex.build").
|
||||||
|
temple_demo-*.tar
|
||||||
|
|
||||||
|
/screenshots/
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# TempleDemo
|
||||||
|
|
||||||
|
To start your Phoenix server:
|
||||||
|
|
||||||
|
* Setup the project with `mix setup`
|
||||||
|
* Start Phoenix endpoint with `mix phx.server`
|
||||||
|
|
||||||
|
Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
|
||||||
|
|
||||||
|
Ready to run in production? Please [check our deployment guides](https://hexdocs.pm/phoenix/deployment.html).
|
||||||
|
|
||||||
|
## Learn more
|
||||||
|
|
||||||
|
* Official website: https://www.phoenixframework.org/
|
||||||
|
* Guides: https://hexdocs.pm/phoenix/overview.html
|
||||||
|
* Docs: https://hexdocs.pm/phoenix
|
||||||
|
* Forum: https://elixirforum.com/c/phoenix-forum
|
||||||
|
* Source: https://github.com/phoenixframework/phoenix
|
|
@ -0,0 +1,42 @@
|
||||||
|
# This file is responsible for configuring your application
|
||||||
|
# and its dependencies with the aid of the Mix.Config module.
|
||||||
|
#
|
||||||
|
# This configuration file is loaded before any dependency and
|
||||||
|
# is restricted to this project.
|
||||||
|
|
||||||
|
# General application configuration
|
||||||
|
use Mix.Config
|
||||||
|
|
||||||
|
config :elixir, :time_zone_database, Tzdata.TimeZoneDatabase
|
||||||
|
|
||||||
|
config :temple_demo,
|
||||||
|
ecto_repos: [TempleDemo.Repo]
|
||||||
|
|
||||||
|
config :phoenix, :template_engines,
|
||||||
|
exs: Temple.Engine
|
||||||
|
|
||||||
|
# Configures the endpoint
|
||||||
|
config :temple_demo, TempleDemoWeb.Endpoint,
|
||||||
|
url: [host: "localhost"],
|
||||||
|
secret_key_base: "ww1nKdikInNFHHUfSdCE1wiTcOmQq/KLvOxG7CY1TlKLDTmLW5yheCCYpfoxmZAW",
|
||||||
|
render_errors: [view: TempleDemoWeb.ErrorView, accepts: ~w(html json), layout: false],
|
||||||
|
pubsub_server: TempleDemo.PubSub,
|
||||||
|
live_view: [signing_salt: "KCU/YIG0"]
|
||||||
|
|
||||||
|
# Configures Elixir's Logger
|
||||||
|
config :logger, :console,
|
||||||
|
format: "$time $metadata[$level] $message\n",
|
||||||
|
metadata: [:request_id]
|
||||||
|
|
||||||
|
# Use Jason for JSON parsing in Phoenix
|
||||||
|
config :phoenix, :json_library, Jason
|
||||||
|
|
||||||
|
config :temple, :aliases,
|
||||||
|
label: :_label,
|
||||||
|
link: :_link,
|
||||||
|
textarea: :_textarea
|
||||||
|
|
||||||
|
|
||||||
|
# Import environment specific config. This must remain at the bottom
|
||||||
|
# of this file so it overrides the configuration defined above.
|
||||||
|
import_config "#{Mix.env()}.exs"
|
|
@ -0,0 +1,68 @@
|
||||||
|
use Mix.Config
|
||||||
|
|
||||||
|
# Configure your database
|
||||||
|
config :temple_demo, TempleDemo.Repo,
|
||||||
|
username: "postgres",
|
||||||
|
password: "postgres",
|
||||||
|
database: "temple_demo_dev",
|
||||||
|
hostname: "localhost",
|
||||||
|
show_sensitive_data_on_connection_error: true,
|
||||||
|
pool_size: 10
|
||||||
|
|
||||||
|
# For development, we disable any cache and enable
|
||||||
|
# debugging and code reloading.
|
||||||
|
#
|
||||||
|
# The watchers configuration can be used to run external
|
||||||
|
# watchers to your application. For example, we use it
|
||||||
|
# with webpack to recompile .js and .css sources.
|
||||||
|
config :temple_demo, TempleDemoWeb.Endpoint,
|
||||||
|
http: [port: 4000],
|
||||||
|
debug_errors: true,
|
||||||
|
code_reloader: true,
|
||||||
|
check_origin: false,
|
||||||
|
watchers: []
|
||||||
|
|
||||||
|
# ## SSL Support
|
||||||
|
#
|
||||||
|
# In order to use HTTPS in development, a self-signed
|
||||||
|
# certificate can be generated by running the following
|
||||||
|
# Mix task:
|
||||||
|
#
|
||||||
|
# mix phx.gen.cert
|
||||||
|
#
|
||||||
|
# Note that this task requires Erlang/OTP 20 or later.
|
||||||
|
# Run `mix help phx.gen.cert` for more information.
|
||||||
|
#
|
||||||
|
# The `http:` config above can be replaced with:
|
||||||
|
#
|
||||||
|
# https: [
|
||||||
|
# port: 4001,
|
||||||
|
# cipher_suite: :strong,
|
||||||
|
# keyfile: "priv/cert/selfsigned_key.pem",
|
||||||
|
# certfile: "priv/cert/selfsigned.pem"
|
||||||
|
# ],
|
||||||
|
#
|
||||||
|
# If desired, both `http:` and `https:` keys can be
|
||||||
|
# configured to run both http and https servers on
|
||||||
|
# different ports.
|
||||||
|
|
||||||
|
# Watch static and templates for browser reloading.
|
||||||
|
config :temple_demo, TempleDemoWeb.Endpoint,
|
||||||
|
live_reload: [
|
||||||
|
patterns: [
|
||||||
|
~r"priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$",
|
||||||
|
~r"priv/gettext/.*(po)$",
|
||||||
|
~r"lib/temple_demo_web/(live|views)/.*(ex)$",
|
||||||
|
~r"lib/temple_demo_web/templates/.*(eex|exs)$"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
# Do not include metadata nor timestamps in development logs
|
||||||
|
config :logger, :console, format: "[$level] $message\n"
|
||||||
|
|
||||||
|
# Set a higher stacktrace during development. Avoid configuring such
|
||||||
|
# in production as building large stacktraces may be expensive.
|
||||||
|
config :phoenix, :stacktrace_depth, 20
|
||||||
|
|
||||||
|
# Initialize plugs at runtime for faster development compilation
|
||||||
|
config :phoenix, :plug_init_mode, :runtime
|
|
@ -0,0 +1,55 @@
|
||||||
|
use Mix.Config
|
||||||
|
|
||||||
|
# For production, don't forget to configure the url host
|
||||||
|
# to something meaningful, Phoenix uses this information
|
||||||
|
# when generating URLs.
|
||||||
|
#
|
||||||
|
# Note we also include the path to a cache manifest
|
||||||
|
# containing the digested version of static files. This
|
||||||
|
# manifest is generated by the `mix phx.digest` task,
|
||||||
|
# which you should run after static files are built and
|
||||||
|
# before starting your production server.
|
||||||
|
config :temple_demo, TempleDemoWeb.Endpoint,
|
||||||
|
url: [host: "example.com", port: 80],
|
||||||
|
cache_static_manifest: "priv/static/cache_manifest.json"
|
||||||
|
|
||||||
|
# Do not print debug messages in production
|
||||||
|
config :logger, level: :info
|
||||||
|
|
||||||
|
# ## SSL Support
|
||||||
|
#
|
||||||
|
# To get SSL working, you will need to add the `https` key
|
||||||
|
# to the previous section and set your `:url` port to 443:
|
||||||
|
#
|
||||||
|
# config :temple_demo, TempleDemoWeb.Endpoint,
|
||||||
|
# ...
|
||||||
|
# url: [host: "example.com", port: 443],
|
||||||
|
# https: [
|
||||||
|
# port: 443,
|
||||||
|
# cipher_suite: :strong,
|
||||||
|
# keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
|
||||||
|
# certfile: System.get_env("SOME_APP_SSL_CERT_PATH"),
|
||||||
|
# transport_options: [socket_opts: [:inet6]]
|
||||||
|
# ]
|
||||||
|
#
|
||||||
|
# The `cipher_suite` is set to `:strong` to support only the
|
||||||
|
# latest and more secure SSL ciphers. This means old browsers
|
||||||
|
# and clients may not be supported. You can set it to
|
||||||
|
# `:compatible` for wider support.
|
||||||
|
#
|
||||||
|
# `:keyfile` and `:certfile` expect an absolute path to the key
|
||||||
|
# and cert in disk or a relative path inside priv, for example
|
||||||
|
# "priv/ssl/server.key". For all supported SSL configuration
|
||||||
|
# options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1
|
||||||
|
#
|
||||||
|
# We also recommend setting `force_ssl` in your endpoint, ensuring
|
||||||
|
# no data is ever sent via http, always redirecting to https:
|
||||||
|
#
|
||||||
|
# config :temple_demo, TempleDemoWeb.Endpoint,
|
||||||
|
# force_ssl: [hsts: true]
|
||||||
|
#
|
||||||
|
# Check `Plug.SSL` for all available options in `force_ssl`.
|
||||||
|
|
||||||
|
# Finally import the config/prod.secret.exs which loads secrets
|
||||||
|
# and configuration from environment variables.
|
||||||
|
import_config "prod.secret.exs"
|
|
@ -0,0 +1,41 @@
|
||||||
|
# In this file, we load production configuration and secrets
|
||||||
|
# from environment variables. You can also hardcode secrets,
|
||||||
|
# although such is generally not recommended and you have to
|
||||||
|
# remember to add this file to your .gitignore.
|
||||||
|
use Mix.Config
|
||||||
|
|
||||||
|
database_url =
|
||||||
|
System.get_env("DATABASE_URL") ||
|
||||||
|
raise """
|
||||||
|
environment variable DATABASE_URL is missing.
|
||||||
|
For example: ecto://USER:PASS@HOST/DATABASE
|
||||||
|
"""
|
||||||
|
|
||||||
|
config :temple_demo, TempleDemo.Repo,
|
||||||
|
# ssl: true,
|
||||||
|
url: database_url,
|
||||||
|
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
|
||||||
|
|
||||||
|
secret_key_base =
|
||||||
|
System.get_env("SECRET_KEY_BASE") ||
|
||||||
|
raise """
|
||||||
|
environment variable SECRET_KEY_BASE is missing.
|
||||||
|
You can generate one by calling: mix phx.gen.secret
|
||||||
|
"""
|
||||||
|
|
||||||
|
config :temple_demo, TempleDemoWeb.Endpoint,
|
||||||
|
http: [
|
||||||
|
port: String.to_integer(System.get_env("PORT") || "4000"),
|
||||||
|
transport_options: [socket_opts: [:inet6]]
|
||||||
|
],
|
||||||
|
secret_key_base: secret_key_base
|
||||||
|
|
||||||
|
# ## Using releases (Elixir v1.9+)
|
||||||
|
#
|
||||||
|
# If you are doing OTP releases, you need to instruct Phoenix
|
||||||
|
# to start each relevant endpoint:
|
||||||
|
#
|
||||||
|
# config :temple_demo, TempleDemoWeb.Endpoint, server: true
|
||||||
|
#
|
||||||
|
# Then you can assemble a release by calling `mix release`.
|
||||||
|
# See `mix help release` for more information.
|
|
@ -0,0 +1,30 @@
|
||||||
|
use Mix.Config
|
||||||
|
|
||||||
|
# Configure your database
|
||||||
|
#
|
||||||
|
# The MIX_TEST_PARTITION environment variable can be used
|
||||||
|
# to provide built-in test partitioning in CI environment.
|
||||||
|
# Run `mix help test` for more information.
|
||||||
|
config :temple_demo, TempleDemo.Repo,
|
||||||
|
username: "postgres",
|
||||||
|
password: "postgres",
|
||||||
|
database: "temple_demo_test#{System.get_env("MIX_TEST_PARTITION")}",
|
||||||
|
hostname: "localhost",
|
||||||
|
pool: Ecto.Adapters.SQL.Sandbox
|
||||||
|
|
||||||
|
# We don't run a server during test. If one is required,
|
||||||
|
# you can enable the server option below.
|
||||||
|
config :temple_demo, TempleDemoWeb.Endpoint,
|
||||||
|
http: [port: 4002],
|
||||||
|
server: true
|
||||||
|
|
||||||
|
config :temple_demo, :sql_sandbox, true
|
||||||
|
|
||||||
|
config :wallaby,
|
||||||
|
base_url: "http://localhost:4002",
|
||||||
|
otp_app: :temple_demo,
|
||||||
|
screenshot_on_failure: true
|
||||||
|
|
||||||
|
|
||||||
|
# Print only warnings and errors during test
|
||||||
|
config :logger, level: :warn
|
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule TempleDemo do
|
||||||
|
@moduledoc """
|
||||||
|
TempleDemo keeps the contexts that define your domain
|
||||||
|
and business logic.
|
||||||
|
|
||||||
|
Contexts are also responsible for managing your data, regardless
|
||||||
|
if it comes from the database, an external API or others.
|
||||||
|
"""
|
||||||
|
end
|
|
@ -0,0 +1,34 @@
|
||||||
|
defmodule TempleDemo.Application do
|
||||||
|
# See https://hexdocs.pm/elixir/Application.html
|
||||||
|
# for more information on OTP Applications
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
use Application
|
||||||
|
|
||||||
|
def start(_type, _args) do
|
||||||
|
children = [
|
||||||
|
# Start the Ecto repository
|
||||||
|
TempleDemo.Repo,
|
||||||
|
# Start the Telemetry supervisor
|
||||||
|
TempleDemoWeb.Telemetry,
|
||||||
|
# Start the PubSub system
|
||||||
|
{Phoenix.PubSub, name: TempleDemo.PubSub},
|
||||||
|
# Start the Endpoint (http/https)
|
||||||
|
TempleDemoWeb.Endpoint
|
||||||
|
# Start a worker by calling: TempleDemo.Worker.start_link(arg)
|
||||||
|
# {TempleDemo.Worker, arg}
|
||||||
|
]
|
||||||
|
|
||||||
|
# See https://hexdocs.pm/elixir/Supervisor.html
|
||||||
|
# for other strategies and supported options
|
||||||
|
opts = [strategy: :one_for_one, name: TempleDemo.Supervisor]
|
||||||
|
Supervisor.start_link(children, opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Tell Phoenix to update the endpoint configuration
|
||||||
|
# whenever the application is updated.
|
||||||
|
def config_change(changed, _new, removed) do
|
||||||
|
TempleDemoWeb.Endpoint.config_change(changed, removed)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,104 @@
|
||||||
|
defmodule TempleDemo.Blog do
|
||||||
|
@moduledoc """
|
||||||
|
The Blog context.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import Ecto.Query, warn: false
|
||||||
|
alias TempleDemo.Repo
|
||||||
|
|
||||||
|
alias TempleDemo.Blog.Post
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the list of posts.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> list_posts()
|
||||||
|
[%Post{}, ...]
|
||||||
|
|
||||||
|
"""
|
||||||
|
def list_posts do
|
||||||
|
Repo.all(Post)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a single post.
|
||||||
|
|
||||||
|
Raises `Ecto.NoResultsError` if the Post does not exist.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> get_post!(123)
|
||||||
|
%Post{}
|
||||||
|
|
||||||
|
iex> get_post!(456)
|
||||||
|
** (Ecto.NoResultsError)
|
||||||
|
|
||||||
|
"""
|
||||||
|
def get_post!(id), do: Repo.get!(Post, id)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Creates a post.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> create_post(%{field: value})
|
||||||
|
{:ok, %Post{}}
|
||||||
|
|
||||||
|
iex> create_post(%{field: bad_value})
|
||||||
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def create_post(attrs \\ %{}) do
|
||||||
|
%Post{}
|
||||||
|
|> Post.changeset(attrs)
|
||||||
|
|> Repo.insert()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Updates a post.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> update_post(post, %{field: new_value})
|
||||||
|
{:ok, %Post{}}
|
||||||
|
|
||||||
|
iex> update_post(post, %{field: bad_value})
|
||||||
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def update_post(%Post{} = post, attrs) do
|
||||||
|
post
|
||||||
|
|> Post.changeset(attrs)
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Deletes a post.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> delete_post(post)
|
||||||
|
{:ok, %Post{}}
|
||||||
|
|
||||||
|
iex> delete_post(post)
|
||||||
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def delete_post(%Post{} = post) do
|
||||||
|
Repo.delete(post)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns an `%Ecto.Changeset{}` for tracking post changes.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> change_post(post)
|
||||||
|
%Ecto.Changeset{data: %Post{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def change_post(%Post{} = post, attrs \\ %{}) do
|
||||||
|
Post.changeset(post, attrs)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,20 @@
|
||||||
|
defmodule TempleDemo.Blog.Post do
|
||||||
|
use Ecto.Schema
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
schema "posts" do
|
||||||
|
field :author, :string
|
||||||
|
field :body, :string
|
||||||
|
field :published_at, :naive_datetime
|
||||||
|
field :title, :string
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def changeset(post, attrs) do
|
||||||
|
post
|
||||||
|
|> cast(attrs, [:title, :body, :published_at, :author])
|
||||||
|
|> validate_required([:title, :body, :published_at, :author])
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
defmodule TempleDemo.Repo do
|
||||||
|
use Ecto.Repo,
|
||||||
|
otp_app: :temple_demo,
|
||||||
|
adapter: Ecto.Adapters.Postgres
|
||||||
|
end
|
|
@ -0,0 +1,80 @@
|
||||||
|
defmodule TempleDemoWeb do
|
||||||
|
@moduledoc """
|
||||||
|
The entrypoint for defining your web interface, such
|
||||||
|
as controllers, views, channels and so on.
|
||||||
|
|
||||||
|
This can be used in your application as:
|
||||||
|
|
||||||
|
use TempleDemoWeb, :controller
|
||||||
|
use TempleDemoWeb, :view
|
||||||
|
|
||||||
|
The definitions below will be executed for every view,
|
||||||
|
controller, etc, so keep them short and clean, focused
|
||||||
|
on imports, uses and aliases.
|
||||||
|
|
||||||
|
Do NOT define functions inside the quoted expressions
|
||||||
|
below. Instead, define any helper function in modules
|
||||||
|
and import those modules here.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def controller do
|
||||||
|
quote do
|
||||||
|
use Phoenix.Controller, namespace: TempleDemoWeb
|
||||||
|
|
||||||
|
import Plug.Conn
|
||||||
|
import TempleDemoWeb.Gettext
|
||||||
|
alias TempleDemoWeb.Router.Helpers, as: Routes
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def view do
|
||||||
|
quote do
|
||||||
|
use Phoenix.View,
|
||||||
|
root: "lib/temple_demo_web/templates",
|
||||||
|
namespace: TempleDemoWeb
|
||||||
|
|
||||||
|
# Import convenience functions from controllers
|
||||||
|
import Phoenix.Controller, only: [get_flash: 1, get_flash: 2, view_module: 1]
|
||||||
|
|
||||||
|
# Include shared imports and aliases for views
|
||||||
|
unquote(view_helpers())
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def router do
|
||||||
|
quote do
|
||||||
|
use Phoenix.Router
|
||||||
|
|
||||||
|
import Plug.Conn
|
||||||
|
import Phoenix.Controller
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def channel do
|
||||||
|
quote do
|
||||||
|
use Phoenix.Channel
|
||||||
|
import TempleDemoWeb.Gettext
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp view_helpers do
|
||||||
|
quote do
|
||||||
|
# Use all HTML functionality (forms, tags, etc)
|
||||||
|
use Phoenix.HTML
|
||||||
|
|
||||||
|
# Import basic rendering functionality (render, render_layout, etc)
|
||||||
|
import Phoenix.View
|
||||||
|
|
||||||
|
import TempleDemoWeb.ErrorHelpers
|
||||||
|
import TempleDemoWeb.Gettext
|
||||||
|
alias TempleDemoWeb.Router.Helpers, as: Routes
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
When used, dispatch to the appropriate controller/view/etc.
|
||||||
|
"""
|
||||||
|
defmacro __using__(which) when is_atom(which) do
|
||||||
|
apply(__MODULE__, which, [])
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,35 @@
|
||||||
|
defmodule TempleDemoWeb.UserSocket do
|
||||||
|
use Phoenix.Socket
|
||||||
|
|
||||||
|
## Channels
|
||||||
|
# channel "room:*", TempleDemoWeb.RoomChannel
|
||||||
|
|
||||||
|
# Socket params are passed from the client and can
|
||||||
|
# be used to verify and authenticate a user. After
|
||||||
|
# verification, you can put default assigns into
|
||||||
|
# the socket that will be set for all channels, ie
|
||||||
|
#
|
||||||
|
# {:ok, assign(socket, :user_id, verified_user_id)}
|
||||||
|
#
|
||||||
|
# To deny connection, return `:error`.
|
||||||
|
#
|
||||||
|
# See `Phoenix.Token` documentation for examples in
|
||||||
|
# performing token verification on connect.
|
||||||
|
@impl true
|
||||||
|
def connect(_params, socket, _connect_info) do
|
||||||
|
{:ok, socket}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Socket id's are topics that allow you to identify all sockets for a given user:
|
||||||
|
#
|
||||||
|
# def id(socket), do: "user_socket:#{socket.assigns.user_id}"
|
||||||
|
#
|
||||||
|
# Would allow you to broadcast a "disconnect" event and terminate
|
||||||
|
# all active sockets and channels for a given user:
|
||||||
|
#
|
||||||
|
# TempleDemoWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
|
||||||
|
#
|
||||||
|
# Returning `nil` makes this socket anonymous.
|
||||||
|
@impl true
|
||||||
|
def id(_socket), do: nil
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
defmodule TempleDemoWeb.PageController do
|
||||||
|
use TempleDemoWeb, :controller
|
||||||
|
|
||||||
|
def index(conn, _params) do
|
||||||
|
render(conn, "index.html")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,63 @@
|
||||||
|
defmodule TempleDemoWeb.PostController do
|
||||||
|
use TempleDemoWeb, :controller
|
||||||
|
|
||||||
|
alias TempleDemo.Blog
|
||||||
|
alias TempleDemo.Blog.Post
|
||||||
|
|
||||||
|
def index(conn, _params) do
|
||||||
|
posts = Blog.list_posts()
|
||||||
|
render(conn, "index.html", posts: posts)
|
||||||
|
end
|
||||||
|
|
||||||
|
def new(conn, _params) do
|
||||||
|
changeset = Blog.change_post(%Post{})
|
||||||
|
|
||||||
|
render(conn, "new.html", changeset: changeset)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(conn, %{"post" => post_params}) do
|
||||||
|
case Blog.create_post(post_params) do
|
||||||
|
{:ok, post} ->
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Post created successfully.")
|
||||||
|
|> redirect(to: Routes.post_path(conn, :show, post))
|
||||||
|
|
||||||
|
{:error, %Ecto.Changeset{} = changeset} ->
|
||||||
|
render(conn, "new.html", changeset: changeset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def show(conn, %{"id" => id}) do
|
||||||
|
post = Blog.get_post!(id)
|
||||||
|
render(conn, "show.html", post: post)
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit(conn, %{"id" => id}) do
|
||||||
|
post = Blog.get_post!(id)
|
||||||
|
changeset = Blog.change_post(post)
|
||||||
|
render(conn, "edit.html", post: post, changeset: changeset)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(conn, %{"id" => id, "post" => post_params}) do
|
||||||
|
post = Blog.get_post!(id)
|
||||||
|
|
||||||
|
case Blog.update_post(post, post_params) do
|
||||||
|
{:ok, post} ->
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Post updated successfully.")
|
||||||
|
|> redirect(to: Routes.post_path(conn, :show, post))
|
||||||
|
|
||||||
|
{:error, %Ecto.Changeset{} = changeset} ->
|
||||||
|
render(conn, "edit.html", post: post, changeset: changeset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(conn, %{"id" => id}) do
|
||||||
|
post = Blog.get_post!(id)
|
||||||
|
{:ok, _post} = Blog.delete_post(post)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Post deleted successfully.")
|
||||||
|
|> redirect(to: Routes.post_path(conn, :index))
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,59 @@
|
||||||
|
defmodule TempleDemoWeb.Endpoint do
|
||||||
|
use Phoenix.Endpoint, otp_app: :temple_demo
|
||||||
|
|
||||||
|
if Application.get_env(:temple_demo, :sql_sandbox) do
|
||||||
|
plug Phoenix.Ecto.SQL.Sandbox
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# The session will be stored in the cookie and signed,
|
||||||
|
# this means its contents can be read but not tampered with.
|
||||||
|
# Set :encryption_salt if you would also like to encrypt it.
|
||||||
|
@session_options [
|
||||||
|
store: :cookie,
|
||||||
|
key: "_temple_demo_key",
|
||||||
|
signing_salt: "p72rbvlQ"
|
||||||
|
]
|
||||||
|
|
||||||
|
socket "/socket", TempleDemoWeb.UserSocket,
|
||||||
|
websocket: true,
|
||||||
|
longpoll: false
|
||||||
|
|
||||||
|
socket "/live", Phoenix.LiveView.Socket, websocket: [connect_info: [session: @session_options]]
|
||||||
|
|
||||||
|
# Serve at "/" the static files from "priv/static" directory.
|
||||||
|
#
|
||||||
|
# You should set gzip to true if you are running phx.digest
|
||||||
|
# when deploying your static files in production.
|
||||||
|
plug Plug.Static,
|
||||||
|
at: "/",
|
||||||
|
from: :temple_demo,
|
||||||
|
gzip: false,
|
||||||
|
only: ~w(css fonts images js favicon.ico robots.txt)
|
||||||
|
|
||||||
|
# Code reloading can be explicitly enabled under the
|
||||||
|
# :code_reloader configuration of your endpoint.
|
||||||
|
if code_reloading? do
|
||||||
|
socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
|
||||||
|
plug Phoenix.LiveReloader
|
||||||
|
plug Phoenix.CodeReloader
|
||||||
|
plug Phoenix.Ecto.CheckRepoStatus, otp_app: :temple_demo
|
||||||
|
end
|
||||||
|
|
||||||
|
plug Phoenix.LiveDashboard.RequestLogger,
|
||||||
|
param_key: "request_logger",
|
||||||
|
cookie_key: "request_logger"
|
||||||
|
|
||||||
|
plug Plug.RequestId
|
||||||
|
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
|
||||||
|
|
||||||
|
plug Plug.Parsers,
|
||||||
|
parsers: [:urlencoded, :multipart, :json],
|
||||||
|
pass: ["*/*"],
|
||||||
|
json_decoder: Phoenix.json_library()
|
||||||
|
|
||||||
|
plug Plug.MethodOverride
|
||||||
|
plug Plug.Head
|
||||||
|
plug Plug.Session, @session_options
|
||||||
|
plug TempleDemoWeb.Router
|
||||||
|
end
|
|
@ -0,0 +1,24 @@
|
||||||
|
defmodule TempleDemoWeb.Gettext do
|
||||||
|
@moduledoc """
|
||||||
|
A module providing Internationalization with a gettext-based API.
|
||||||
|
|
||||||
|
By using [Gettext](https://hexdocs.pm/gettext),
|
||||||
|
your module gains a set of macros for translations, for example:
|
||||||
|
|
||||||
|
import TempleDemoWeb.Gettext
|
||||||
|
|
||||||
|
# Simple translation
|
||||||
|
gettext("Here is the string to translate")
|
||||||
|
|
||||||
|
# Plural translation
|
||||||
|
ngettext("Here is the string to translate",
|
||||||
|
"Here are the strings to translate",
|
||||||
|
3)
|
||||||
|
|
||||||
|
# Domain-based translation
|
||||||
|
dgettext("errors", "Here is the error message to translate")
|
||||||
|
|
||||||
|
See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
|
||||||
|
"""
|
||||||
|
use Gettext, otp_app: :temple_demo
|
||||||
|
end
|
|
@ -0,0 +1,43 @@
|
||||||
|
defmodule TempleDemoWeb.Router do
|
||||||
|
use TempleDemoWeb, :router
|
||||||
|
|
||||||
|
pipeline :browser do
|
||||||
|
plug :accepts, ["html"]
|
||||||
|
plug :fetch_session
|
||||||
|
plug :fetch_flash
|
||||||
|
plug :protect_from_forgery
|
||||||
|
plug :put_secure_browser_headers
|
||||||
|
end
|
||||||
|
|
||||||
|
pipeline :api do
|
||||||
|
plug :accepts, ["json"]
|
||||||
|
end
|
||||||
|
|
||||||
|
scope "/", TempleDemoWeb do
|
||||||
|
pipe_through :browser
|
||||||
|
|
||||||
|
get "/", PageController, :index
|
||||||
|
resources "/posts", PostController
|
||||||
|
end
|
||||||
|
|
||||||
|
# Other scopes may use custom stacks.
|
||||||
|
# scope "/api", TempleDemoWeb do
|
||||||
|
# pipe_through :api
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Enables LiveDashboard only for development
|
||||||
|
#
|
||||||
|
# If you want to use the LiveDashboard in production, you should put
|
||||||
|
# it behind authentication and allow only admins to access it.
|
||||||
|
# If your application does not have an admins-only section yet,
|
||||||
|
# you can use Plug.BasicAuth to set up some basic authentication
|
||||||
|
# as long as you are also using SSL (which you should anyway).
|
||||||
|
if Mix.env() in [:dev, :test] do
|
||||||
|
import Phoenix.LiveDashboard.Router
|
||||||
|
|
||||||
|
scope "/" do
|
||||||
|
pipe_through :browser
|
||||||
|
live_dashboard "/dashboard", metrics: TempleDemoWeb.Telemetry
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,53 @@
|
||||||
|
defmodule TempleDemoWeb.Telemetry do
|
||||||
|
use Supervisor
|
||||||
|
import Telemetry.Metrics
|
||||||
|
|
||||||
|
def start_link(arg) do
|
||||||
|
Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def init(_arg) do
|
||||||
|
children = [
|
||||||
|
{:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
|
||||||
|
# Add reporters as children of your supervision tree.
|
||||||
|
# {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
|
||||||
|
]
|
||||||
|
|
||||||
|
Supervisor.init(children, strategy: :one_for_one)
|
||||||
|
end
|
||||||
|
|
||||||
|
def metrics do
|
||||||
|
[
|
||||||
|
# Phoenix Metrics
|
||||||
|
summary("phoenix.endpoint.stop.duration",
|
||||||
|
unit: {:native, :millisecond}
|
||||||
|
),
|
||||||
|
summary("phoenix.router_dispatch.stop.duration",
|
||||||
|
tags: [:route],
|
||||||
|
unit: {:native, :millisecond}
|
||||||
|
),
|
||||||
|
|
||||||
|
# Database Metrics
|
||||||
|
summary("temple_demo.repo.query.total_time", unit: {:native, :millisecond}),
|
||||||
|
summary("temple_demo.repo.query.decode_time", unit: {:native, :millisecond}),
|
||||||
|
summary("temple_demo.repo.query.query_time", unit: {:native, :millisecond}),
|
||||||
|
summary("temple_demo.repo.query.queue_time", unit: {:native, :millisecond}),
|
||||||
|
summary("temple_demo.repo.query.idle_time", unit: {:native, :millisecond}),
|
||||||
|
|
||||||
|
# VM Metrics
|
||||||
|
summary("vm.memory.total", unit: {:byte, :kilobyte}),
|
||||||
|
summary("vm.total_run_queue_lengths.total"),
|
||||||
|
summary("vm.total_run_queue_lengths.cpu"),
|
||||||
|
summary("vm.total_run_queue_lengths.io")
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp periodic_measurements do
|
||||||
|
[
|
||||||
|
# A module, function and arguments to be invoked periodically.
|
||||||
|
# This function must call :telemetry.execute/3 and a metric must be added above.
|
||||||
|
# {TempleDemoWeb, :count_users, []}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,41 @@
|
||||||
|
html lang: "en" do
|
||||||
|
head do
|
||||||
|
meta charset: "utf-8"
|
||||||
|
meta http_equiv: "X-UA-Compatible", content: "IE=edge"
|
||||||
|
meta name: "viewport", content: "width=device-width, initial-scale=1.0"
|
||||||
|
title do: "TempleDemo · Phoenix Framework"
|
||||||
|
|
||||||
|
_link(rel: "stylesheet", href: Routes.static_path(@conn, "/css/app.css"))
|
||||||
|
end
|
||||||
|
|
||||||
|
body do
|
||||||
|
header do
|
||||||
|
section class: "container" do
|
||||||
|
nav role: "navigation" do
|
||||||
|
ul do
|
||||||
|
li do
|
||||||
|
a href: "https://hexdocs.pm/phoenix/overview.html" do
|
||||||
|
"Get Started"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
a href: "http://phoenixframework.org/", class: "phx-logo" do
|
||||||
|
img src: Routes.static_path(@conn, "/images/phoenix.png"),
|
||||||
|
alt: "Phoenix Framework Logo"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
main role: "main", class: "container" do
|
||||||
|
p class: "alert alert-info", role: "alert", compact: true, do: get_flash(@conn, :info)
|
||||||
|
p class: "alert alert-danger", role: "alert", compact: true, do: get_flash(@conn, :error)
|
||||||
|
|
||||||
|
@inner_content
|
||||||
|
end
|
||||||
|
|
||||||
|
script type: "text/javascript", src: Routes.static_path(@conn, "/js/phoenix_html.js")
|
||||||
|
script type: "text/javascript", src: Routes.static_path(@conn, "/js/app.js")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,65 @@
|
||||||
|
section class: "phx-hero" do
|
||||||
|
h1 do
|
||||||
|
gettext("Welcome to %{name}!", name: "Phoenix")
|
||||||
|
end
|
||||||
|
|
||||||
|
p do
|
||||||
|
"Peace-of-mind from prototype to production"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
section class: "row" do
|
||||||
|
article class: "column" do
|
||||||
|
h2 do: "Resources"
|
||||||
|
|
||||||
|
ul do
|
||||||
|
li do
|
||||||
|
a href: "https://hexdocs.pm/phoenix/overview.html" do
|
||||||
|
"Guides & Docs"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
li do
|
||||||
|
a href: "https://github.com/phoenixframework/phoenix" do
|
||||||
|
"Source"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
li do
|
||||||
|
a href: "https://github.com/phoenixframework/phoenix/blob/v1.5/CHANGELOG.md" do
|
||||||
|
"v1.5 Changelog"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
article class: "column" do
|
||||||
|
h2 do: "Help"
|
||||||
|
|
||||||
|
ul do
|
||||||
|
li do
|
||||||
|
a href: "https://elixirforum.com/c/phoenix-forum" do
|
||||||
|
"Forum"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
li do
|
||||||
|
a href: "https://webchat.freenode.net/?channels=elixir-lang" do
|
||||||
|
"#elixir-lang on Freenode IRC"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
li do
|
||||||
|
a href: "https://twitter.com/elixirphoenix" do
|
||||||
|
"Twitter @elixirphoenix"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
li do
|
||||||
|
a href: "https://elixir-slackin.herokuapp.com/" do
|
||||||
|
"Elixir on Slack"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
h1 do: "Edit Post"
|
||||||
|
|
||||||
|
render("form.html", Map.put(assigns, :action, Routes.post_path(@conn, :update, @post)))
|
||||||
|
|
||||||
|
span do
|
||||||
|
link "Back", to: Routes.post_path(@conn, :index)
|
||||||
|
end
|
|
@ -0,0 +1,27 @@
|
||||||
|
form_for @changeset, @action, fn f ->
|
||||||
|
if @changeset.action do
|
||||||
|
div class: "alert alert-danger" do
|
||||||
|
p do: "Oops, something went wrong! Please check the errors below."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
label f, :title
|
||||||
|
text_input f, :title
|
||||||
|
error_tag(f, :title)
|
||||||
|
|
||||||
|
label f, :body
|
||||||
|
textarea f, :body
|
||||||
|
error_tag(f, :body)
|
||||||
|
|
||||||
|
label f, :published_at
|
||||||
|
datetime_select f, :published_at
|
||||||
|
error_tag(f, :published_at)
|
||||||
|
|
||||||
|
label f, :author
|
||||||
|
text_input f, :author
|
||||||
|
error_tag(f, :author)
|
||||||
|
|
||||||
|
div do
|
||||||
|
submit "Save"
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,33 @@
|
||||||
|
h1 do: "Listing Posts"
|
||||||
|
|
||||||
|
table do
|
||||||
|
thead do
|
||||||
|
tr do
|
||||||
|
th do: "Title"
|
||||||
|
th do: "Body"
|
||||||
|
th do: "Published at"
|
||||||
|
th do: "Author"
|
||||||
|
th()
|
||||||
|
end
|
||||||
|
tbody do
|
||||||
|
for post <- @posts do
|
||||||
|
tr do
|
||||||
|
td do: post.title
|
||||||
|
td do: post.body
|
||||||
|
td do: post.published_at
|
||||||
|
td do: post.author
|
||||||
|
td do
|
||||||
|
link "Show", to: Routes.post_path(@conn, :show, post)
|
||||||
|
link "Edit", to: Routes.post_path(@conn, :edit, post)
|
||||||
|
link "Delete", to: Routes.post_path(@conn, :delete, post),
|
||||||
|
method: :delete, data: [confirm: "Are you sure?"]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
span do
|
||||||
|
link "New Post", to: Routes.post_path(@conn, :new)
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
h1 do: "New Post"
|
||||||
|
|
||||||
|
render("form.html", Map.put(assigns, :action, Routes.post_path(@conn, :create)))
|
||||||
|
|
||||||
|
span do
|
||||||
|
link "Back", to: Routes.post_path(@conn, :index)
|
||||||
|
end
|
|
@ -0,0 +1,25 @@
|
||||||
|
h1 do: "Show Post"
|
||||||
|
|
||||||
|
ul do
|
||||||
|
li do: [strong(do: "Title"), @post.title]
|
||||||
|
li do
|
||||||
|
strong do: "Body"
|
||||||
|
Phoenix.HTML.Format.text_to_html @post.body, attributes: [class: "whitespace-pre"]
|
||||||
|
end
|
||||||
|
li do
|
||||||
|
strong do: "Published at"
|
||||||
|
@post.published_at
|
||||||
|
end
|
||||||
|
li do
|
||||||
|
strong do: "Author"
|
||||||
|
@post.author
|
||||||
|
end
|
||||||
|
|
||||||
|
span do
|
||||||
|
link "Edit", to: Routes.post_path(@conn, :edit, @post)
|
||||||
|
end
|
||||||
|
|
||||||
|
span do
|
||||||
|
link "Back", to: Routes.post_path(@conn, :index)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,47 @@
|
||||||
|
defmodule TempleDemoWeb.ErrorHelpers do
|
||||||
|
@moduledoc """
|
||||||
|
Conveniences for translating and building error messages.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Phoenix.HTML
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Generates tag for inlined form input errors.
|
||||||
|
"""
|
||||||
|
def error_tag(form, field) do
|
||||||
|
Enum.map(Keyword.get_values(form.errors, field), fn error ->
|
||||||
|
content_tag(:span, translate_error(error),
|
||||||
|
class: "invalid-feedback",
|
||||||
|
phx_feedback_for: input_id(form, field)
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Translates an error message using gettext.
|
||||||
|
"""
|
||||||
|
def translate_error({msg, opts}) do
|
||||||
|
# When using gettext, we typically pass the strings we want
|
||||||
|
# to translate as a static argument:
|
||||||
|
#
|
||||||
|
# # Translate "is invalid" in the "errors" domain
|
||||||
|
# dgettext("errors", "is invalid")
|
||||||
|
#
|
||||||
|
# # Translate the number of files with plural rules
|
||||||
|
# dngettext("errors", "1 file", "%{count} files", count)
|
||||||
|
#
|
||||||
|
# Because the error messages we show in our forms and APIs
|
||||||
|
# are defined inside Ecto, we need to translate them dynamically.
|
||||||
|
# This requires us to call the Gettext module passing our gettext
|
||||||
|
# backend as first argument.
|
||||||
|
#
|
||||||
|
# Note we use the "errors" domain, which means translations
|
||||||
|
# should be written to the errors.po file. The :count option is
|
||||||
|
# set by Ecto and indicates we should also apply plural rules.
|
||||||
|
if count = opts[:count] do
|
||||||
|
Gettext.dngettext(TempleDemoWeb.Gettext, "errors", msg, msg, count, opts)
|
||||||
|
else
|
||||||
|
Gettext.dgettext(TempleDemoWeb.Gettext, "errors", msg, opts)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,16 @@
|
||||||
|
defmodule TempleDemoWeb.ErrorView do
|
||||||
|
use TempleDemoWeb, :view
|
||||||
|
|
||||||
|
# If you want to customize a particular status code
|
||||||
|
# for a certain format, you may uncomment below.
|
||||||
|
# def render("500.html", _assigns) do
|
||||||
|
# "Internal Server Error"
|
||||||
|
# end
|
||||||
|
|
||||||
|
# By default, Phoenix returns the status message from
|
||||||
|
# the template name. For example, "404.html" becomes
|
||||||
|
# "Not Found".
|
||||||
|
def template_not_found(template, _assigns) do
|
||||||
|
Phoenix.Controller.status_message_from_template(template)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule TempleDemoWeb.LayoutView do
|
||||||
|
use TempleDemoWeb, :view
|
||||||
|
end
|
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule TempleDemoWeb.PageView do
|
||||||
|
use TempleDemoWeb, :view
|
||||||
|
end
|
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule TempleDemoWeb.PostView do
|
||||||
|
use TempleDemoWeb, :view
|
||||||
|
end
|
|
@ -0,0 +1,68 @@
|
||||||
|
defmodule TempleDemo.MixProject do
|
||||||
|
use Mix.Project
|
||||||
|
|
||||||
|
def project do
|
||||||
|
[
|
||||||
|
app: :temple_demo,
|
||||||
|
version: "0.1.0",
|
||||||
|
elixir: "~> 1.7",
|
||||||
|
elixirc_paths: elixirc_paths(Mix.env()),
|
||||||
|
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
|
||||||
|
start_permanent: Mix.env() == :prod,
|
||||||
|
aliases: aliases(),
|
||||||
|
deps: deps()
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Configuration for the OTP application.
|
||||||
|
#
|
||||||
|
# Type `mix help compile.app` for more information.
|
||||||
|
def application do
|
||||||
|
[
|
||||||
|
mod: {TempleDemo.Application, []},
|
||||||
|
extra_applications: [:logger, :runtime_tools]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Specifies which paths to compile per environment.
|
||||||
|
defp elixirc_paths(:test), do: ["lib", "test/support"]
|
||||||
|
defp elixirc_paths(_), do: ["lib"]
|
||||||
|
|
||||||
|
# Specifies your project dependencies.
|
||||||
|
#
|
||||||
|
# Type `mix help deps` for examples and options.
|
||||||
|
defp deps do
|
||||||
|
[
|
||||||
|
{:phoenix, "~> 1.5.1"},
|
||||||
|
{:phoenix_ecto, "~> 4.1"},
|
||||||
|
{:ecto_sql, "~> 3.4"},
|
||||||
|
{:postgrex, ">= 0.0.0"},
|
||||||
|
{:phoenix_html, "~> 2.11"},
|
||||||
|
{:phoenix_live_reload, "~> 1.2", only: :dev},
|
||||||
|
{:phoenix_live_dashboard, "~> 0.2.0"},
|
||||||
|
{:telemetry_metrics, "~> 0.4"},
|
||||||
|
{:telemetry_poller, "~> 0.4"},
|
||||||
|
{:gettext, "~> 0.11"},
|
||||||
|
{:jason, "~> 1.0"},
|
||||||
|
{:plug_cowboy, "~> 2.0"},
|
||||||
|
{:wallaby, "~> 0.26.0", only: :test},
|
||||||
|
{:tzdata, "~> 1.0.3"},
|
||||||
|
{:temple, path: "../../"}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Aliases are shortcuts or tasks specific to the current project.
|
||||||
|
# For example, to install project dependencies and perform other setup tasks, run:
|
||||||
|
#
|
||||||
|
# $ mix setup
|
||||||
|
#
|
||||||
|
# See the documentation for `Mix` for more info on aliases.
|
||||||
|
defp aliases do
|
||||||
|
[
|
||||||
|
setup: ["deps.get", "ecto.setup"],
|
||||||
|
"ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
|
||||||
|
"ecto.reset": ["ecto.drop", "ecto.setup"],
|
||||||
|
test: ["ecto.create --quiet", "ecto.migrate", "test"]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,42 @@
|
||||||
|
%{
|
||||||
|
"certifi": {:hex, :certifi, "2.5.2", "b7cfeae9d2ed395695dd8201c57a2d019c0c43ecaf8b8bcb9320b40d6662f340", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "3b3b5f36493004ac3455966991eaf6e768ce9884693d9968055aeeeb1e575040"},
|
||||||
|
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"},
|
||||||
|
"cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"},
|
||||||
|
"cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"},
|
||||||
|
"db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"},
|
||||||
|
"decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
|
||||||
|
"ecto": {:hex, :ecto, "3.4.5", "2bcd262f57b2c888b0bd7f7a28c8a48aa11dc1a2c6a858e45dd8f8426d504265", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8c6d1d4d524559e9b7a062f0498e2c206122552d63eacff0a6567ffe7a8e8691"},
|
||||||
|
"ecto_sql": {:hex, :ecto_sql, "3.4.4", "d28bac2d420f708993baed522054870086fd45016a9d09bb2cd521b9c48d32ea", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "edb49af715dd72f213b66adfd0f668a43c17ed510b5d9ac7528569b23af57fe8"},
|
||||||
|
"file_system": {:hex, :file_system, "0.2.8", "f632bd287927a1eed2b718f22af727c5aeaccc9a98d8c2bd7bff709e851dc986", [:mix], [], "hexpm", "97a3b6f8d63ef53bd0113070102db2ce05352ecf0d25390eb8d747c2bde98bca"},
|
||||||
|
"gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"},
|
||||||
|
"hackney": {:hex, :hackney, "1.16.0", "5096ac8e823e3a441477b2d187e30dd3fff1a82991a806b2003845ce72ce2d84", [:rebar3], [{:certifi, "2.5.2", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.1", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.0", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.6", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "3bf0bebbd5d3092a3543b783bf065165fa5d3ad4b899b836810e513064134e18"},
|
||||||
|
"httpoison": {:hex, :httpoison, "1.7.0", "abba7d086233c2d8574726227b6c2c4f6e53c4deae7fe5f6de531162ce9929a0", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "975cc87c845a103d3d1ea1ccfd68a2700c211a434d8428b10c323dc95dc5b980"},
|
||||||
|
"idna": {:hex, :idna, "6.0.1", "1d038fb2e7668ce41fbf681d2c45902e52b3cb9e9c77b55334353b222c2ee50c", [:rebar3], [{:unicode_util_compat, "0.5.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a02c8a1c4fd601215bb0b0324c8a6986749f807ce35f25449ec9e69758708122"},
|
||||||
|
"jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"},
|
||||||
|
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
||||||
|
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"},
|
||||||
|
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
|
||||||
|
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
|
||||||
|
"phoenix": {:hex, :phoenix, "1.5.2", "7ba05d6cb0024eefd3cb08b176e6f041a9edff094912de2f6a49e3ba67140fb3", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3047022367d415a935dceda1176e67d9c7f2d41cd52a0419b53cfca66fc4c64e"},
|
||||||
|
"phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"},
|
||||||
|
"phoenix_html": {:hex, :phoenix_html, "2.14.2", "b8a3899a72050f3f48a36430da507dd99caf0ac2d06c77529b1646964f3d563e", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "58061c8dfd25da5df1ea0ca47c972f161beb6c875cd293917045b92ffe1bf617"},
|
||||||
|
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.2.4", "3080e8a89bab3ec08d4dd9a6858dfa24af9334464aae78c83e58a2db37c6f983", [:mix], [{:phoenix_html, "~> 2.14.1 or ~> 2.15", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.12.0 or ~> 0.13.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.4.0 or ~> 0.5.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1c89595ef60f1b76ac07705e73f001823af451491792a4b0d5b2b2a3789b0a00"},
|
||||||
|
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.2", "38d94c30df5e2ef11000697a4fbe2b38d0fbf79239d492ff1be87bbc33bc3a84", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "a3dec3d28ddb5476c96a7c8a38ea8437923408bc88da43e5c45d97037b396280"},
|
||||||
|
"phoenix_live_view": {:hex, :phoenix_live_view, "0.13.0", "dec006b3da4ab164283d5bebe960724eb4d19cd0ed553e05fb99b260233e200f", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.4.17 or ~> 1.5.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "bd6f13b666fa9bfeca88b013db20414c693d5a5e6d19b1fc2602c282d626ed8e"},
|
||||||
|
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
|
||||||
|
"plug": {:hex, :plug, "1.10.3", "c9cebe917637d8db0e759039cc106adca069874e1a9034fd6e3fdd427fd3c283", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "01f9037a2a1de1d633b5a881101e6a444bcabb1d386ca1e00bb273a1f1d9d939"},
|
||||||
|
"plug_cowboy": {:hex, :plug_cowboy, "2.2.1", "fcf58aa33227a4322a050e4783ee99c63c031a2e7f9a2eb7340d55505e17f30f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3b43de24460d87c0971887286e7a20d40462e48eb7235954681a20cee25ddeb6"},
|
||||||
|
"plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},
|
||||||
|
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
|
||||||
|
"postgrex": {:hex, :postgrex, "0.15.5", "aec40306a622d459b01bff890fa42f1430dac61593b122754144ad9033a2152f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ed90c81e1525f65a2ba2279dbcebf030d6d13328daa2f8088b9661eb9143af7f"},
|
||||||
|
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
|
||||||
|
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
|
||||||
|
"telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
|
||||||
|
"telemetry_metrics": {:hex, :telemetry_metrics, "0.5.0", "1b796e74add83abf844e808564275dfb342bcc930b04c7577ab780e262b0d998", [:mix], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "31225e6ce7a37a421a0a96ec55244386aec1c190b22578bd245188a4a33298fd"},
|
||||||
|
"telemetry_poller": {:hex, :telemetry_poller, "0.5.0", "4770888ef85599ead39c7f51d6b4b62306e602d96c69b2625d54dea3d9a5204b", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69e4e8e65b0ae077c9e14cd5f42c7cc486de0e07ac6e3409e6f0e52699a7872c"},
|
||||||
|
"tesla": {:hex, :tesla, "1.3.3", "26ae98627af5c406584aa6755ab5fc96315d70d69a24dd7f8369cfcb75094a45", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2648f1c276102f9250299e0b7b57f3071c67827349d9173f34c281756a1b124c"},
|
||||||
|
"tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a6e1ee7003c4d04ecbd21dd3ec690d4c6662db5d3bbdd7262d53cdf5e7c746c1"},
|
||||||
|
"unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"},
|
||||||
|
"wallaby": {:hex, :wallaby, "0.26.0", "170b05b2fe572ec38071dbe45a908123959d5245f389f657e9a79eb463dc0431", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}, {:httpoison, "~> 0.12 or ~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix_ecto, ">= 3.0.0", [hex: :phoenix_ecto, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:web_driver_client, "~> 0.1.0", [hex: :web_driver_client, repo: "hexpm", optional: false]}], "hexpm", "07a437e75c9276900288e4fe5c1814a5486f10a54940aa524ea65ce22b40c182"},
|
||||||
|
"web_driver_client": {:hex, :web_driver_client, "0.1.0", "19466a989c76b7ec803c796cec0fec4611a64f445fd5120ce50c9e3817e09c2c", [:mix], [{:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:tesla, "~> 1.3.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm", "c9c031ca915e8fc75b5e24ac93503244f3cc406dd7f53047087a45aa62d60e9e"},
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
## `msgid`s in this file come from POT (.pot) files.
|
||||||
|
##
|
||||||
|
## Do not add, change, or remove `msgid`s manually here as
|
||||||
|
## they're tied to the ones in the corresponding POT file
|
||||||
|
## (with the same domain).
|
||||||
|
##
|
||||||
|
## Use `mix gettext.extract --merge` or `mix gettext.merge`
|
||||||
|
## to merge POT files into PO files.
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Language: en\n"
|
||||||
|
|
||||||
|
## From Ecto.Changeset.cast/4
|
||||||
|
msgid "can't be blank"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.unique_constraint/3
|
||||||
|
msgid "has already been taken"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.put_change/3
|
||||||
|
msgid "is invalid"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_acceptance/3
|
||||||
|
msgid "must be accepted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_format/3
|
||||||
|
msgid "has invalid format"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_subset/3
|
||||||
|
msgid "has an invalid entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_exclusion/3
|
||||||
|
msgid "is reserved"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_confirmation/3
|
||||||
|
msgid "does not match confirmation"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.no_assoc_constraint/3
|
||||||
|
msgid "is still associated with this entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "are still associated with this entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_length/3
|
||||||
|
msgid "should be %{count} character(s)"
|
||||||
|
msgid_plural "should be %{count} character(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "should have %{count} item(s)"
|
||||||
|
msgid_plural "should have %{count} item(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "should be at least %{count} character(s)"
|
||||||
|
msgid_plural "should be at least %{count} character(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "should have at least %{count} item(s)"
|
||||||
|
msgid_plural "should have at least %{count} item(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "should be at most %{count} character(s)"
|
||||||
|
msgid_plural "should be at most %{count} character(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "should have at most %{count} item(s)"
|
||||||
|
msgid_plural "should have at most %{count} item(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_number/3
|
||||||
|
msgid "must be less than %{number}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "must be greater than %{number}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "must be less than or equal to %{number}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "must be greater than or equal to %{number}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "must be equal to %{number}"
|
||||||
|
msgstr ""
|
|
@ -0,0 +1,95 @@
|
||||||
|
## This is a PO Template file.
|
||||||
|
##
|
||||||
|
## `msgid`s here are often extracted from source code.
|
||||||
|
## Add new translations manually only if they're dynamic
|
||||||
|
## translations that can't be statically extracted.
|
||||||
|
##
|
||||||
|
## Run `mix gettext.extract` to bring this file up to
|
||||||
|
## date. Leave `msgstr`s empty as changing them here has no
|
||||||
|
## effect: edit them in PO (`.po`) files instead.
|
||||||
|
|
||||||
|
## From Ecto.Changeset.cast/4
|
||||||
|
msgid "can't be blank"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.unique_constraint/3
|
||||||
|
msgid "has already been taken"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.put_change/3
|
||||||
|
msgid "is invalid"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_acceptance/3
|
||||||
|
msgid "must be accepted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_format/3
|
||||||
|
msgid "has invalid format"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_subset/3
|
||||||
|
msgid "has an invalid entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_exclusion/3
|
||||||
|
msgid "is reserved"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_confirmation/3
|
||||||
|
msgid "does not match confirmation"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.no_assoc_constraint/3
|
||||||
|
msgid "is still associated with this entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "are still associated with this entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_length/3
|
||||||
|
msgid "should be %{count} character(s)"
|
||||||
|
msgid_plural "should be %{count} character(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "should have %{count} item(s)"
|
||||||
|
msgid_plural "should have %{count} item(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "should be at least %{count} character(s)"
|
||||||
|
msgid_plural "should be at least %{count} character(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "should have at least %{count} item(s)"
|
||||||
|
msgid_plural "should have at least %{count} item(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "should be at most %{count} character(s)"
|
||||||
|
msgid_plural "should be at most %{count} character(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
msgid "should have at most %{count} item(s)"
|
||||||
|
msgid_plural "should have at most %{count} item(s)"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
## From Ecto.Changeset.validate_number/3
|
||||||
|
msgid "must be less than %{number}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "must be greater than %{number}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "must be less than or equal to %{number}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "must be greater than or equal to %{number}"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "must be equal to %{number}"
|
||||||
|
msgstr ""
|
|
@ -0,0 +1,4 @@
|
||||||
|
[
|
||||||
|
import_deps: [:ecto_sql],
|
||||||
|
inputs: ["*.exs"]
|
||||||
|
]
|
|
@ -0,0 +1,15 @@
|
||||||
|
defmodule TempleDemo.Repo.Migrations.CreatePosts do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:posts) do
|
||||||
|
add :title, :string
|
||||||
|
add :body, :text
|
||||||
|
add :published_at, :naive_datetime
|
||||||
|
add :author, :string
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Script for populating the database. You can run it as:
|
||||||
|
#
|
||||||
|
# mix run priv/repo/seeds.exs
|
||||||
|
#
|
||||||
|
# Inside the script, you can read and write to any of your
|
||||||
|
# repositories directly:
|
||||||
|
#
|
||||||
|
# TempleDemo.Repo.insert!(%TempleDemo.SomeSchema{})
|
||||||
|
#
|
||||||
|
# We recommend using the bang functions (`insert!`, `update!`
|
||||||
|
# and so on) as they will fail if something goes wrong.
|
|
@ -0,0 +1,40 @@
|
||||||
|
/* This file is for your main application css. */
|
||||||
|
@import "./phoenix.css";
|
||||||
|
|
||||||
|
.invalid-feedback {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-info {
|
||||||
|
color: #31708f;
|
||||||
|
background-color: #d9edf7;
|
||||||
|
border-color: #bce8f1;
|
||||||
|
}
|
||||||
|
.alert-warning {
|
||||||
|
color: #8a6d3b;
|
||||||
|
background-color: #fcf8e3;
|
||||||
|
border-color: #faebcc;
|
||||||
|
}
|
||||||
|
.alert-danger {
|
||||||
|
color: #a94442;
|
||||||
|
background-color: #f2dede;
|
||||||
|
border-color: #ebccd1;
|
||||||
|
}
|
||||||
|
.alert p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
|
@ -0,0 +1,3 @@
|
||||||
|
// for phoenix_html support, including form and button helpers
|
||||||
|
// copy the following scripts into your javascript bundle:
|
||||||
|
// * deps/phoenix_html/priv/static/phoenix_html.js
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,76 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
var PolyfillEvent = eventConstructor();
|
||||||
|
|
||||||
|
function eventConstructor() {
|
||||||
|
if (typeof window.CustomEvent === "function") return window.CustomEvent;
|
||||||
|
// IE<=9 Support
|
||||||
|
function CustomEvent(event, params) {
|
||||||
|
params = params || {bubbles: false, cancelable: false, detail: undefined};
|
||||||
|
var evt = document.createEvent('CustomEvent');
|
||||||
|
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
|
||||||
|
return evt;
|
||||||
|
}
|
||||||
|
CustomEvent.prototype = window.Event.prototype;
|
||||||
|
return CustomEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildHiddenInput(name, value) {
|
||||||
|
var input = document.createElement("input");
|
||||||
|
input.type = "hidden";
|
||||||
|
input.name = name;
|
||||||
|
input.value = value;
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClick(element) {
|
||||||
|
var to = element.getAttribute("data-to"),
|
||||||
|
method = buildHiddenInput("_method", element.getAttribute("data-method")),
|
||||||
|
csrf = buildHiddenInput("_csrf_token", element.getAttribute("data-csrf")),
|
||||||
|
form = document.createElement("form"),
|
||||||
|
target = element.getAttribute("target");
|
||||||
|
|
||||||
|
form.method = (element.getAttribute("data-method") === "get") ? "get" : "post";
|
||||||
|
form.action = to;
|
||||||
|
form.style.display = "hidden";
|
||||||
|
|
||||||
|
if (target) form.target = target;
|
||||||
|
|
||||||
|
form.appendChild(csrf);
|
||||||
|
form.appendChild(method);
|
||||||
|
document.body.appendChild(form);
|
||||||
|
form.submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("click", function(e) {
|
||||||
|
var element = e.target;
|
||||||
|
|
||||||
|
while (element && element.getAttribute) {
|
||||||
|
var phoenixLinkEvent = new PolyfillEvent('phoenix.link.click', {
|
||||||
|
"bubbles": true, "cancelable": true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!element.dispatchEvent(phoenixLinkEvent)) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.getAttribute("data-method")) {
|
||||||
|
handleClick(element);
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
element = element.parentNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
window.addEventListener('phoenix.link.click', function (e) {
|
||||||
|
var message = e.target.getAttribute("data-confirm");
|
||||||
|
if(message && !window.confirm(message)) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
}, false);
|
||||||
|
})();
|
|
@ -0,0 +1,5 @@
|
||||||
|
# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
|
||||||
|
#
|
||||||
|
# To ban all spiders from the entire site uncomment the next two lines:
|
||||||
|
# User-agent: *
|
||||||
|
# Disallow: /
|
|
@ -0,0 +1,40 @@
|
||||||
|
defmodule TempleDemoWeb.ChannelCase do
|
||||||
|
@moduledoc """
|
||||||
|
This module defines the test case to be used by
|
||||||
|
channel tests.
|
||||||
|
|
||||||
|
Such tests rely on `Phoenix.ChannelTest` and also
|
||||||
|
import other functionality to make it easier
|
||||||
|
to build common data structures and query the data layer.
|
||||||
|
|
||||||
|
Finally, if the test case interacts with the database,
|
||||||
|
we enable the SQL sandbox, so changes done to the database
|
||||||
|
are reverted at the end of every test. If you are using
|
||||||
|
PostgreSQL, you can even run database tests asynchronously
|
||||||
|
by setting `use TempleDemoWeb.ChannelCase, async: true`, although
|
||||||
|
this option is not recommended for other databases.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use ExUnit.CaseTemplate
|
||||||
|
|
||||||
|
using do
|
||||||
|
quote do
|
||||||
|
# Import conveniences for testing with channels
|
||||||
|
import Phoenix.ChannelTest
|
||||||
|
import TempleDemoWeb.ChannelCase
|
||||||
|
|
||||||
|
# The default endpoint for testing
|
||||||
|
@endpoint TempleDemoWeb.Endpoint
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
setup tags do
|
||||||
|
:ok = Ecto.Adapters.SQL.Sandbox.checkout(TempleDemo.Repo)
|
||||||
|
|
||||||
|
unless tags[:async] do
|
||||||
|
Ecto.Adapters.SQL.Sandbox.mode(TempleDemo.Repo, {:shared, self()})
|
||||||
|
end
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,43 @@
|
||||||
|
defmodule TempleDemoWeb.ConnCase do
|
||||||
|
@moduledoc """
|
||||||
|
This module defines the test case to be used by
|
||||||
|
tests that require setting up a connection.
|
||||||
|
|
||||||
|
Such tests rely on `Phoenix.ConnTest` and also
|
||||||
|
import other functionality to make it easier
|
||||||
|
to build common data structures and query the data layer.
|
||||||
|
|
||||||
|
Finally, if the test case interacts with the database,
|
||||||
|
we enable the SQL sandbox, so changes done to the database
|
||||||
|
are reverted at the end of every test. If you are using
|
||||||
|
PostgreSQL, you can even run database tests asynchronously
|
||||||
|
by setting `use TempleDemoWeb.ConnCase, async: true`, although
|
||||||
|
this option is not recommended for other databases.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use ExUnit.CaseTemplate
|
||||||
|
|
||||||
|
using do
|
||||||
|
quote do
|
||||||
|
# Import conveniences for testing with connections
|
||||||
|
import Plug.Conn
|
||||||
|
import Phoenix.ConnTest
|
||||||
|
import TempleDemoWeb.ConnCase
|
||||||
|
|
||||||
|
alias TempleDemoWeb.Router.Helpers, as: Routes
|
||||||
|
|
||||||
|
# The default endpoint for testing
|
||||||
|
@endpoint TempleDemoWeb.Endpoint
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
setup tags do
|
||||||
|
:ok = Ecto.Adapters.SQL.Sandbox.checkout(TempleDemo.Repo)
|
||||||
|
|
||||||
|
unless tags[:async] do
|
||||||
|
Ecto.Adapters.SQL.Sandbox.mode(TempleDemo.Repo, {:shared, self()})
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, conn: Phoenix.ConnTest.build_conn()}
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,55 @@
|
||||||
|
defmodule TempleDemo.DataCase do
|
||||||
|
@moduledoc """
|
||||||
|
This module defines the setup for tests requiring
|
||||||
|
access to the application's data layer.
|
||||||
|
|
||||||
|
You may define functions here to be used as helpers in
|
||||||
|
your tests.
|
||||||
|
|
||||||
|
Finally, if the test case interacts with the database,
|
||||||
|
we enable the SQL sandbox, so changes done to the database
|
||||||
|
are reverted at the end of every test. If you are using
|
||||||
|
PostgreSQL, you can even run database tests asynchronously
|
||||||
|
by setting `use TempleDemo.DataCase, async: true`, although
|
||||||
|
this option is not recommended for other databases.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use ExUnit.CaseTemplate
|
||||||
|
|
||||||
|
using do
|
||||||
|
quote do
|
||||||
|
alias TempleDemo.Repo
|
||||||
|
|
||||||
|
import Ecto
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Ecto.Query
|
||||||
|
import TempleDemo.DataCase
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
setup tags do
|
||||||
|
:ok = Ecto.Adapters.SQL.Sandbox.checkout(TempleDemo.Repo)
|
||||||
|
|
||||||
|
unless tags[:async] do
|
||||||
|
Ecto.Adapters.SQL.Sandbox.mode(TempleDemo.Repo, {:shared, self()})
|
||||||
|
end
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
A helper that transforms changeset errors into a map of messages.
|
||||||
|
|
||||||
|
assert {:error, changeset} = Accounts.create_user(%{password: "short"})
|
||||||
|
assert "password is too short" in errors_on(changeset).password
|
||||||
|
assert %{password: ["password is too short"]} = errors_on(changeset)
|
||||||
|
|
||||||
|
"""
|
||||||
|
def errors_on(changeset) do
|
||||||
|
Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
|
||||||
|
Regex.replace(~r"%{(\w+)}", message, fn _, key ->
|
||||||
|
opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,70 @@
|
||||||
|
defmodule TempleDemo.BlogTest do
|
||||||
|
use TempleDemo.DataCase
|
||||||
|
|
||||||
|
alias TempleDemo.Blog
|
||||||
|
|
||||||
|
describe "posts" do
|
||||||
|
alias TempleDemo.Blog.Post
|
||||||
|
|
||||||
|
@valid_attrs %{author: "some author", body: "some body", published_at: ~N[2010-04-17 14:00:00], title: "some title"}
|
||||||
|
@update_attrs %{author: "some updated author", body: "some updated body", published_at: ~N[2011-05-18 15:01:01], title: "some updated title"}
|
||||||
|
@invalid_attrs %{author: nil, body: nil, published_at: nil, title: nil}
|
||||||
|
|
||||||
|
def post_fixture(attrs \\ %{}) do
|
||||||
|
{:ok, post} =
|
||||||
|
attrs
|
||||||
|
|> Enum.into(@valid_attrs)
|
||||||
|
|> Blog.create_post()
|
||||||
|
|
||||||
|
post
|
||||||
|
end
|
||||||
|
|
||||||
|
test "list_posts/0 returns all posts" do
|
||||||
|
post = post_fixture()
|
||||||
|
assert Blog.list_posts() == [post]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_post!/1 returns the post with given id" do
|
||||||
|
post = post_fixture()
|
||||||
|
assert Blog.get_post!(post.id) == post
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create_post/1 with valid data creates a post" do
|
||||||
|
assert {:ok, %Post{} = post} = Blog.create_post(@valid_attrs)
|
||||||
|
assert post.author == "some author"
|
||||||
|
assert post.body == "some body"
|
||||||
|
assert post.published_at == ~N[2010-04-17 14:00:00]
|
||||||
|
assert post.title == "some title"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create_post/1 with invalid data returns error changeset" do
|
||||||
|
assert {:error, %Ecto.Changeset{}} = Blog.create_post(@invalid_attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update_post/2 with valid data updates the post" do
|
||||||
|
post = post_fixture()
|
||||||
|
assert {:ok, %Post{} = post} = Blog.update_post(post, @update_attrs)
|
||||||
|
assert post.author == "some updated author"
|
||||||
|
assert post.body == "some updated body"
|
||||||
|
assert post.published_at == ~N[2011-05-18 15:01:01]
|
||||||
|
assert post.title == "some updated title"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update_post/2 with invalid data returns error changeset" do
|
||||||
|
post = post_fixture()
|
||||||
|
assert {:error, %Ecto.Changeset{}} = Blog.update_post(post, @invalid_attrs)
|
||||||
|
assert post == Blog.get_post!(post.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "delete_post/1 deletes the post" do
|
||||||
|
post = post_fixture()
|
||||||
|
assert {:ok, %Post{}} = Blog.delete_post(post)
|
||||||
|
assert_raise Ecto.NoResultsError, fn -> Blog.get_post!(post.id) end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "change_post/1 returns a post changeset" do
|
||||||
|
post = post_fixture()
|
||||||
|
assert %Ecto.Changeset{} = Blog.change_post(post)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,8 @@
|
||||||
|
defmodule TempleDemoWeb.PageControllerTest do
|
||||||
|
use TempleDemoWeb.ConnCase
|
||||||
|
|
||||||
|
test "GET /", %{conn: conn} do
|
||||||
|
conn = get(conn, "/")
|
||||||
|
assert html_response(conn, 200) =~ "Welcome to Phoenix!"
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,88 @@
|
||||||
|
defmodule TempleDemoWeb.PostControllerTest do
|
||||||
|
use TempleDemoWeb.ConnCase
|
||||||
|
|
||||||
|
alias TempleDemo.Blog
|
||||||
|
|
||||||
|
@create_attrs %{author: "some author", body: "some body", published_at: ~N[2010-04-17 14:00:00], title: "some title"}
|
||||||
|
@update_attrs %{author: "some updated author", body: "some updated body", published_at: ~N[2011-05-18 15:01:01], title: "some updated title"}
|
||||||
|
@invalid_attrs %{author: nil, body: nil, published_at: nil, title: nil}
|
||||||
|
|
||||||
|
def fixture(:post) do
|
||||||
|
{:ok, post} = Blog.create_post(@create_attrs)
|
||||||
|
post
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "index" do
|
||||||
|
test "lists all posts", %{conn: conn} do
|
||||||
|
conn = get(conn, Routes.post_path(conn, :index))
|
||||||
|
assert html_response(conn, 200) =~ "Listing Posts"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "new post" do
|
||||||
|
test "renders form", %{conn: conn} do
|
||||||
|
conn = get(conn, Routes.post_path(conn, :new))
|
||||||
|
assert html_response(conn, 200) =~ "New Post"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "create post" do
|
||||||
|
test "redirects to show when data is valid", %{conn: conn} do
|
||||||
|
conn = post(conn, Routes.post_path(conn, :create), post: @create_attrs)
|
||||||
|
|
||||||
|
assert %{id: id} = redirected_params(conn)
|
||||||
|
assert redirected_to(conn) == Routes.post_path(conn, :show, id)
|
||||||
|
|
||||||
|
conn = get(conn, Routes.post_path(conn, :show, id))
|
||||||
|
assert html_response(conn, 200) =~ "Show Post"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders errors when data is invalid", %{conn: conn} do
|
||||||
|
conn = post(conn, Routes.post_path(conn, :create), post: @invalid_attrs)
|
||||||
|
assert html_response(conn, 200) =~ "New Post"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "edit post" do
|
||||||
|
setup [:create_post]
|
||||||
|
|
||||||
|
test "renders form for editing chosen post", %{conn: conn, post: post} do
|
||||||
|
conn = get(conn, Routes.post_path(conn, :edit, post))
|
||||||
|
assert html_response(conn, 200) =~ "Edit Post"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "update post" do
|
||||||
|
setup [:create_post]
|
||||||
|
|
||||||
|
test "redirects when data is valid", %{conn: conn, post: post} do
|
||||||
|
conn = put(conn, Routes.post_path(conn, :update, post), post: @update_attrs)
|
||||||
|
assert redirected_to(conn) == Routes.post_path(conn, :show, post)
|
||||||
|
|
||||||
|
conn = get(conn, Routes.post_path(conn, :show, post))
|
||||||
|
assert html_response(conn, 200) =~ "some updated author"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders errors when data is invalid", %{conn: conn, post: post} do
|
||||||
|
conn = put(conn, Routes.post_path(conn, :update, post), post: @invalid_attrs)
|
||||||
|
assert html_response(conn, 200) =~ "Edit Post"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "delete post" do
|
||||||
|
setup [:create_post]
|
||||||
|
|
||||||
|
test "deletes chosen post", %{conn: conn, post: post} do
|
||||||
|
conn = delete(conn, Routes.post_path(conn, :delete, post))
|
||||||
|
assert redirected_to(conn) == Routes.post_path(conn, :index)
|
||||||
|
assert_error_sent 404, fn ->
|
||||||
|
get(conn, Routes.post_path(conn, :show, post))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_post(_) do
|
||||||
|
post = fixture(:post)
|
||||||
|
{:ok, post: post}
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,32 @@
|
||||||
|
defmodule TempleDemoWeb.TempleFeatureTest do
|
||||||
|
use ExUnit.Case, async: false
|
||||||
|
use Wallaby.Feature
|
||||||
|
alias TempleDemoWeb.Router.Helpers, as: Routes
|
||||||
|
alias TempleDemoWeb.Endpoint, as: E
|
||||||
|
|
||||||
|
feature "renders the homepage", %{session: session} do
|
||||||
|
session
|
||||||
|
|> visit("/")
|
||||||
|
|> assert_text("Welcome to Phoenix!")
|
||||||
|
end
|
||||||
|
|
||||||
|
feature "can create a new post", %{session: session} do
|
||||||
|
session
|
||||||
|
|> visit(Routes.post_path(E, :index))
|
||||||
|
|> click(Query.link("New Post"))
|
||||||
|
|> fill_in(Query.text_field("Title"), with: "Temple is awesome!")
|
||||||
|
|> fill_in(Query.text_field("Body"), with: "In this post I will show you how to use Temple")
|
||||||
|
|> find(Query.select("post_published_at_year"), fn s ->
|
||||||
|
s |> click(Query.option("2020"))
|
||||||
|
end)
|
||||||
|
|> find(Query.select("post_published_at_month"), fn s ->
|
||||||
|
s |> click(Query.option("May"))
|
||||||
|
end)
|
||||||
|
|> find(Query.select("post_published_at_day"), fn s ->
|
||||||
|
s |> click(Query.option("21"))
|
||||||
|
end)
|
||||||
|
|> fill_in(Query.text_field("Author"), with: "Mitchelob Ultra")
|
||||||
|
|> click(Query.button("Save"))
|
||||||
|
|> assert_text("Post created successfully.")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,14 @@
|
||||||
|
defmodule TempleDemoWeb.ErrorViewTest do
|
||||||
|
use TempleDemoWeb.ConnCase, async: true
|
||||||
|
|
||||||
|
# Bring render/3 and render_to_string/3 for testing custom views
|
||||||
|
import Phoenix.View
|
||||||
|
|
||||||
|
test "renders 404.html" do
|
||||||
|
assert render_to_string(TempleDemoWeb.ErrorView, "404.html", []) == "Not Found"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders 500.html" do
|
||||||
|
assert render_to_string(TempleDemoWeb.ErrorView, "500.html", []) == "Internal Server Error"
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,8 @@
|
||||||
|
defmodule TempleDemoWeb.LayoutViewTest do
|
||||||
|
use TempleDemoWeb.ConnCase, async: true
|
||||||
|
|
||||||
|
# When testing helpers, you may want to import Phoenix.HTML and
|
||||||
|
# use functions such as safe_to_string() to convert the helper
|
||||||
|
# result into an HTML string.
|
||||||
|
# import Phoenix.HTML
|
||||||
|
end
|
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule TempleDemoWeb.PageViewTest do
|
||||||
|
use TempleDemoWeb.ConnCase, async: true
|
||||||
|
end
|
|
@ -0,0 +1,3 @@
|
||||||
|
Ecto.Adapters.SQL.Sandbox.mode(TempleDemo.Repo, :manual)
|
||||||
|
|
||||||
|
ExUnit.start()
|
|
@ -0,0 +1,32 @@
|
||||||
|
defmodule Temple.Buffer do
|
||||||
|
@moduledoc false
|
||||||
|
def start_link(state \\ []) do
|
||||||
|
Agent.start_link(fn -> state end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def put(buffer, value) do
|
||||||
|
Agent.update(buffer, fn b -> [value | b] end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_new_line(buffer) do
|
||||||
|
Agent.update(buffer, fn
|
||||||
|
["\n" | rest] ->
|
||||||
|
rest
|
||||||
|
|
||||||
|
rest ->
|
||||||
|
rest
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def get(buffer) do
|
||||||
|
buffer
|
||||||
|
|> Agent.get(& &1)
|
||||||
|
|> Enum.reverse()
|
||||||
|
|> Enum.join()
|
||||||
|
|> String.trim()
|
||||||
|
end
|
||||||
|
|
||||||
|
def stop(buffer) do
|
||||||
|
Agent.stop(buffer)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,23 +0,0 @@
|
||||||
defmodule Mix.Tasks.Temple.Convert do
|
|
||||||
use Mix.Task
|
|
||||||
@preferred_cli_env :dev
|
|
||||||
@shortdoc "Converts HTML to Temple syntax"
|
|
||||||
@moduledoc """
|
|
||||||
Converts HTML to Temple syntax
|
|
||||||
|
|
||||||
Takes HTML from a file or from stdin and outputs temple syntax to stdout.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def run(args) do
|
|
||||||
html =
|
|
||||||
if Enum.count(args) > 0 do
|
|
||||||
args |> List.first() |> File.read!()
|
|
||||||
else
|
|
||||||
IO.read(:stdio, :all)
|
|
||||||
end
|
|
||||||
|
|
||||||
{:ok, result} = Temple.HtmlToTemple.parse(html)
|
|
||||||
|
|
||||||
IO.write(result)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -213,51 +213,51 @@ if Code.ensure_loaded?(Mix.Phoenix) do
|
||||||
{nil, nil, nil}
|
{nil, nil, nil}
|
||||||
|
|
||||||
{key, :integer} ->
|
{key, :integer} ->
|
||||||
{label(key), ~s(number_input form, #{inspect(key)}), error(key)}
|
{label(key), ~s(number_input f, #{inspect(key)}), error(key)}
|
||||||
|
|
||||||
{key, :float} ->
|
{key, :float} ->
|
||||||
{label(key), ~s(number_input form, #{inspect(key)}, step: "any"), error(key)}
|
{label(key), ~s(number_input f, #{inspect(key)}, step: "any"), error(key)}
|
||||||
|
|
||||||
{key, :decimal} ->
|
{key, :decimal} ->
|
||||||
{label(key), ~s(number_input form, #{inspect(key)}, step: "any"), error(key)}
|
{label(key), ~s(number_input f, #{inspect(key)}, step: "any"), error(key)}
|
||||||
|
|
||||||
{key, :boolean} ->
|
{key, :boolean} ->
|
||||||
{label(key), ~s(checkbox form, #{inspect(key)}), error(key)}
|
{label(key), ~s(checkbox f, #{inspect(key)}), error(key)}
|
||||||
|
|
||||||
{key, :text} ->
|
{key, :text} ->
|
||||||
{label(key), ~s(textarea form, #{inspect(key)}), error(key)}
|
{label(key), ~s(textarea f, #{inspect(key)}), error(key)}
|
||||||
|
|
||||||
{key, :date} ->
|
{key, :date} ->
|
||||||
{label(key), ~s(date_select form, #{inspect(key)}), error(key)}
|
{label(key), ~s(date_select f, #{inspect(key)}), error(key)}
|
||||||
|
|
||||||
{key, :time} ->
|
{key, :time} ->
|
||||||
{label(key), ~s(time_select form, #{inspect(key)}), error(key)}
|
{label(key), ~s(time_select f, #{inspect(key)}), error(key)}
|
||||||
|
|
||||||
{key, :utc_datetime} ->
|
{key, :utc_datetime} ->
|
||||||
{label(key), ~s(datetime_select form, #{inspect(key)}), error(key)}
|
{label(key), ~s(datetime_select f, #{inspect(key)}), error(key)}
|
||||||
|
|
||||||
{key, :naive_datetime} ->
|
{key, :naive_datetime} ->
|
||||||
{label(key), ~s(datetime_select form, #{inspect(key)}), error(key)}
|
{label(key), ~s(datetime_select f, #{inspect(key)}), error(key)}
|
||||||
|
|
||||||
{key, {:array, :integer}} ->
|
{key, {:array, :integer}} ->
|
||||||
{label(key), ~s(multiple_select form, #{inspect(key)}, ["1": 1, "2": 2]), error(key)}
|
{label(key), ~s(multiple_select f, #{inspect(key)}, ["1": 1, "2": 2]), error(key)}
|
||||||
|
|
||||||
{key, {:array, _}} ->
|
{key, {:array, _}} ->
|
||||||
{label(key),
|
{label(key),
|
||||||
~s(multiple_select form, #{inspect(key)}, ["Option 1": "option1", "Option 2": "option2"]),
|
~s(multiple_select f, #{inspect(key)}, ["Option 1": "option1", "Option 2": "option2"]),
|
||||||
error(key)}
|
error(key)}
|
||||||
|
|
||||||
{key, _} ->
|
{key, _} ->
|
||||||
{label(key), ~s(text_input form, #{inspect(key)}), error(key)}
|
{label(key), ~s(text_input f, #{inspect(key)}), error(key)}
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp label(key) do
|
defp label(key) do
|
||||||
~s(phx_label form, #{inspect(key)})
|
~s(label f, #{inspect(key)})
|
||||||
end
|
end
|
||||||
|
|
||||||
defp error(field) do
|
defp error(field) do
|
||||||
~s{partial error_tag(form, #{inspect(field)})}
|
~s{error_tag(f, #{inspect(field)})}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
539
lib/temple.ex
539
lib/temple.ex
|
@ -1,182 +1,423 @@
|
||||||
defmodule Temple do
|
defmodule Temple do
|
||||||
|
alias Temple.Buffer
|
||||||
|
|
||||||
|
@moduledoc """
|
||||||
|
> Warning: Docs are WIP
|
||||||
|
|
||||||
|
Temple syntax is available inside the `temple` and `live_temple` macros, and is compiled into EEx at build time.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
temple do
|
||||||
|
# You can define attributes by passing a keyword list to the element, the values can be literals or variables.
|
||||||
|
class = "text-blue"
|
||||||
|
id = "jumbotron"
|
||||||
|
|
||||||
|
div class: class, id: id do
|
||||||
|
# Text nodes can be emitted as string literals or variables.
|
||||||
|
"Bob"
|
||||||
|
|
||||||
|
id
|
||||||
|
end
|
||||||
|
|
||||||
|
# if and unless expressions can be used to conditionally render content
|
||||||
|
if 5 > 0 do
|
||||||
|
p do
|
||||||
|
"Greater than 0!"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
unless 5 > 0 do
|
||||||
|
p do
|
||||||
|
"Less than 0!"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# You can loop over items using for comprehensions
|
||||||
|
for x <- 0..5 do
|
||||||
|
div do
|
||||||
|
x
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# You can use multiline anonymous functions, like if you're building a form in Phoenix
|
||||||
|
form_for @changeset, Routes.user_path(@conn, :create), fn f ->
|
||||||
|
"Name: "
|
||||||
|
text_input f, :name
|
||||||
|
end
|
||||||
|
|
||||||
|
# You can explicitly call a tag by prefixing with the Temple module
|
||||||
|
Temple.div do
|
||||||
|
"Foo"
|
||||||
|
end
|
||||||
|
|
||||||
|
# You can also pass children as a do key instead of a block
|
||||||
|
div do: "Alice", class: "text-yellow"
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reserved keywords
|
||||||
|
|
||||||
|
You can pass a keyword list to an element as element attributes, but there are several reserved keywords.
|
||||||
|
|
||||||
|
#### Compact
|
||||||
|
|
||||||
|
Passing `compact: true` will not rendering new lines from within the element. This is useful if you are trying to use the `:empty` psuedo selector.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
temple do
|
||||||
|
p compact: true do
|
||||||
|
"Foo"
|
||||||
|
end
|
||||||
|
p do
|
||||||
|
"Bar"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
would evaluate to
|
||||||
|
|
||||||
|
```html
|
||||||
|
<p>Foo</p>
|
||||||
|
<p>
|
||||||
|
Bar
|
||||||
|
</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
#### Aliases
|
||||||
|
|
||||||
|
You can add an alias for an element if there is a namespace collision with a function. If you are using `Phoenix.HTML`, there will be namespace collisions with the `<link>` and `<label>` elements.
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
config :temple, :aliases,
|
||||||
|
label: :_label,
|
||||||
|
link: :_link
|
||||||
|
|
||||||
|
temple do
|
||||||
|
_label do
|
||||||
|
"Email"
|
||||||
|
end
|
||||||
|
|
||||||
|
_link href: "/css/site.css"
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
This will result in:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<label>
|
||||||
|
Email
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<link href="/css/site.css">
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
defmacro __using__(_) do
|
defmacro __using__(_) do
|
||||||
quote location: :keep do
|
quote location: :keep do
|
||||||
import Temple
|
import Temple
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
defmodule Private do
|
||||||
Creates a markup context.
|
@moduledoc false
|
||||||
|
@aliases Application.get_env(:temple, :aliases, [])
|
||||||
|
|
||||||
All tags must be called inside of a `Temple.temple/1` block.
|
@nonvoid_elements ~w[
|
||||||
|
head title style script
|
||||||
|
noscript template
|
||||||
|
body section nav article aside h1 h2 h3 h4 h5 h6
|
||||||
|
header footer address main
|
||||||
|
p pre blockquote ol ul li dl dt dd figure figcaption div
|
||||||
|
a em strong small s cite q dfn abbr data time code var samp kbd
|
||||||
|
sub sup i b u mark ruby rt rp bdi bdo span
|
||||||
|
ins del
|
||||||
|
iframe object video audio canvas
|
||||||
|
map
|
||||||
|
table caption colgroup tbody thead tfoot tr td th
|
||||||
|
form fieldset legend label button select datalist optgroup
|
||||||
|
option textarea output progress meter
|
||||||
|
details summary menuitem menu
|
||||||
|
html
|
||||||
|
]a
|
||||||
|
|
||||||
Returns a safe result of the form `{:safe, result}`
|
@nonvoid_elements_aliases Enum.map(@nonvoid_elements, fn el ->
|
||||||
|
Keyword.get(@aliases, el, el)
|
||||||
|
end)
|
||||||
|
@nonvoid_elements_lookup Enum.map(@nonvoid_elements, fn el ->
|
||||||
|
{Keyword.get(@aliases, el, el), el}
|
||||||
|
end)
|
||||||
|
|
||||||
## Example
|
@void_elements ~w[
|
||||||
|
meta link base
|
||||||
|
area br col embed hr img input keygen param source track wbr
|
||||||
|
]a
|
||||||
|
|
||||||
```
|
@void_elements_aliases Enum.map(@void_elements, fn el -> Keyword.get(@aliases, el, el) end)
|
||||||
team = ["Alice", "Bob", "Carol"]
|
@void_elements_lookup Enum.map(@void_elements, fn el ->
|
||||||
|
{Keyword.get(@aliases, el, el), el}
|
||||||
|
end)
|
||||||
|
|
||||||
temple do
|
def snake_to_kebab(stringable),
|
||||||
for name <- team do
|
do:
|
||||||
div class: "text-bold" do
|
stringable |> to_string() |> String.replace_trailing("_", "") |> String.replace("_", "-")
|
||||||
text name
|
|
||||||
end
|
def kebab_to_snake(stringable),
|
||||||
|
do: stringable |> to_string() |> String.replace("-", "_")
|
||||||
|
|
||||||
|
def compile_attrs([]), do: ""
|
||||||
|
|
||||||
|
def compile_attrs([attrs]) when is_list(attrs) do
|
||||||
|
compile_attrs(attrs)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
# {:safe, "<div class=\"text-bold\">Alice</div><div class=\"text-bold\">Bob</div><div class=\"text-bold\">Carol</div>"}
|
def compile_attrs(attrs) do
|
||||||
```
|
for {name, value} <- attrs, into: "" do
|
||||||
"""
|
name = snake_to_kebab(name)
|
||||||
defmacro temple([do: block] = _block) do
|
|
||||||
quote location: :keep do
|
|
||||||
import Kernel, except: [div: 2, use: 1, use: 2]
|
|
||||||
import Temple.Html
|
|
||||||
import Temple.Svg
|
|
||||||
import Temple.Form
|
|
||||||
import Temple.Link
|
|
||||||
|
|
||||||
with {:ok, var!(buff, Temple.Html)} <- Temple.Utils.start_buffer([]) do
|
case value do
|
||||||
unquote(block)
|
{_, _, _} = macro ->
|
||||||
|
" " <> name <> "=\"<%= " <> Macro.to_string(macro) <> " %>\""
|
||||||
|
|
||||||
markup = Temple.Utils.get_buffer(var!(buff, Temple.Html))
|
value ->
|
||||||
|
" " <> name <> "=\"" <> to_string(value) <> "\""
|
||||||
:ok = Temple.Utils.stop_buffer(var!(buff, Temple.Html))
|
|
||||||
|
|
||||||
Temple.Utils.join_and_escape(markup)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Emits a text node into the markup.
|
|
||||||
|
|
||||||
```
|
|
||||||
temple do
|
|
||||||
div do
|
|
||||||
text "Hello, world!"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# {:safe, "<div>Hello, world!</div>"}
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
defmacro text(text) do
|
|
||||||
quote location: :keep do
|
|
||||||
Temple.Utils.put_buffer(
|
|
||||||
var!(buff, Temple.Html),
|
|
||||||
unquote(text) |> Temple.Utils.escape_content()
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Emits a Phoenix partial into the markup.
|
|
||||||
|
|
||||||
```
|
|
||||||
temple 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
|
end
|
||||||
|
|
||||||
script type: "text/javascript", src: Routes.static_path(@conn, "/js/app.js")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def split_args(nil), do: {[], []}
|
||||||
|
|
||||||
|
def split_args(args) do
|
||||||
|
{do_and_else, args} =
|
||||||
|
args
|
||||||
|
|> Enum.split_with(fn
|
||||||
|
arg when is_list(arg) ->
|
||||||
|
(Keyword.keys(arg) -- [:do, :else]) |> Enum.count() == 0
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end)
|
||||||
|
|
||||||
|
{List.flatten(do_and_else), args}
|
||||||
|
end
|
||||||
|
|
||||||
|
def split_on_fn([{:fn, _, _} = func | rest], {args, _, args2}) do
|
||||||
|
split_on_fn(rest, {args, func, args2})
|
||||||
|
end
|
||||||
|
|
||||||
|
def split_on_fn([arg | rest], {args, nil, args2}) do
|
||||||
|
split_on_fn(rest, {[arg | args], nil, args2})
|
||||||
|
end
|
||||||
|
|
||||||
|
def split_on_fn([arg | rest], {args, func, args2}) do
|
||||||
|
split_on_fn(rest, {args, func, [arg | args2]})
|
||||||
|
end
|
||||||
|
|
||||||
|
def split_on_fn([], {args, func, args2}) do
|
||||||
|
{Enum.reverse(args), func, Enum.reverse(args2)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def pop_compact?([]), do: {false, []}
|
||||||
|
def pop_compact?([args]) when is_list(args), do: pop_compact?(args)
|
||||||
|
|
||||||
|
def pop_compact?(args) do
|
||||||
|
Keyword.pop(args, :compact, false)
|
||||||
|
end
|
||||||
|
|
||||||
|
def traverse(buffer, {:__block__, _meta, block}) do
|
||||||
|
traverse(buffer, block)
|
||||||
|
end
|
||||||
|
|
||||||
|
def traverse(buffer, {name, meta, args} = macro) do
|
||||||
|
{do_and_else, args} =
|
||||||
|
args
|
||||||
|
|> split_args()
|
||||||
|
|
||||||
|
includes_fn? = args |> Enum.any?(fn x -> match?({:fn, _, _}, x) end)
|
||||||
|
|
||||||
|
case name do
|
||||||
|
{:., _, [{:__aliases__, _, [:Temple]}, name]} when name in @nonvoid_elements_aliases ->
|
||||||
|
{do_and_else, args} =
|
||||||
|
case args do
|
||||||
|
[args] ->
|
||||||
|
{do_value, args} = Keyword.pop(args, :do)
|
||||||
|
|
||||||
|
do_and_else = Keyword.put_new(do_and_else, :do, do_value)
|
||||||
|
|
||||||
|
{do_and_else, args}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{do_and_else, args}
|
||||||
|
end
|
||||||
|
|
||||||
|
name = @nonvoid_elements_lookup[name]
|
||||||
|
|
||||||
|
{compact?, args} = pop_compact?(args)
|
||||||
|
|
||||||
|
Buffer.put(buffer, "<#{name}#{compile_attrs(args)}>")
|
||||||
|
unless compact?, do: Buffer.put(buffer, "\n")
|
||||||
|
traverse(buffer, do_and_else[:do])
|
||||||
|
if compact?, do: Buffer.remove_new_line(buffer)
|
||||||
|
Buffer.put(buffer, "</#{name}>")
|
||||||
|
Buffer.put(buffer, "\n")
|
||||||
|
|
||||||
|
{:., _, [{:__aliases__, _, [:Temple]}, name]} when name in @void_elements_aliases ->
|
||||||
|
name = @void_elements_lookup[name]
|
||||||
|
|
||||||
|
Buffer.put(buffer, "<#{name}#{compile_attrs(args)}>")
|
||||||
|
Buffer.put(buffer, "\n")
|
||||||
|
|
||||||
|
name when name in @nonvoid_elements_aliases ->
|
||||||
|
{do_and_else, args} =
|
||||||
|
case args do
|
||||||
|
[args] ->
|
||||||
|
{do_value, args} = Keyword.pop(args, :do)
|
||||||
|
|
||||||
|
do_and_else = Keyword.put_new(do_and_else, :do, do_value)
|
||||||
|
|
||||||
|
{do_and_else, args}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{do_and_else, args}
|
||||||
|
end
|
||||||
|
|
||||||
|
name = @nonvoid_elements_lookup[name]
|
||||||
|
|
||||||
|
{compact?, args} = pop_compact?(args)
|
||||||
|
|
||||||
|
Buffer.put(buffer, "<#{name}#{compile_attrs(args)}>")
|
||||||
|
unless compact?, do: Buffer.put(buffer, "\n")
|
||||||
|
traverse(buffer, do_and_else[:do])
|
||||||
|
if compact?, do: Buffer.remove_new_line(buffer)
|
||||||
|
Buffer.put(buffer, "</#{name}>")
|
||||||
|
Buffer.put(buffer, "\n")
|
||||||
|
|
||||||
|
name when name in @void_elements_aliases ->
|
||||||
|
name = @void_elements_lookup[name]
|
||||||
|
|
||||||
|
Buffer.put(buffer, "<#{name}#{compile_attrs(args)}>")
|
||||||
|
Buffer.put(buffer, "\n")
|
||||||
|
|
||||||
|
name when includes_fn? ->
|
||||||
|
{args, func_arg, args2} = split_on_fn(args, {[], nil, []})
|
||||||
|
|
||||||
|
{func, _, [{arrow, _, [[{arg, _, _}], block]}]} = func_arg
|
||||||
|
|
||||||
|
Buffer.put(
|
||||||
|
buffer,
|
||||||
|
"<%= " <>
|
||||||
|
to_string(name) <>
|
||||||
|
" " <>
|
||||||
|
(Enum.map(args, &Macro.to_string(&1)) |> Enum.join(", ")) <>
|
||||||
|
", " <>
|
||||||
|
to_string(func) <> " " <> to_string(arg) <> " " <> to_string(arrow) <> " %>"
|
||||||
|
)
|
||||||
|
|
||||||
|
Buffer.put(buffer, "\n")
|
||||||
|
|
||||||
|
traverse(buffer, block)
|
||||||
|
|
||||||
|
if Enum.any?(args2) do
|
||||||
|
Buffer.put(
|
||||||
|
buffer,
|
||||||
|
"<% end, " <>
|
||||||
|
(Enum.map(args2, fn arg -> Macro.to_string(arg) end)
|
||||||
|
|> Enum.join(", ")) <> " %>"
|
||||||
|
)
|
||||||
|
|
||||||
|
Buffer.put(buffer, "\n")
|
||||||
|
else
|
||||||
|
Buffer.put(buffer, "<% end %>")
|
||||||
|
Buffer.put(buffer, "\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
name when name in [:for, :if, :unless] ->
|
||||||
|
Buffer.put(buffer, "<%= " <> Macro.to_string({name, meta, args}) <> " do %>")
|
||||||
|
Buffer.put(buffer, "\n")
|
||||||
|
|
||||||
|
traverse(buffer, do_and_else[:do])
|
||||||
|
|
||||||
|
if Keyword.has_key?(do_and_else, :else) do
|
||||||
|
Buffer.put(buffer, "<% else %>")
|
||||||
|
Buffer.put(buffer, "\n")
|
||||||
|
traverse(buffer, do_and_else[:else])
|
||||||
|
end
|
||||||
|
|
||||||
|
Buffer.put(buffer, "<% end %>")
|
||||||
|
Buffer.put(buffer, "\n")
|
||||||
|
|
||||||
|
name when name in [:=] ->
|
||||||
|
Buffer.put(buffer, "<% " <> Macro.to_string(macro) <> " %>")
|
||||||
|
Buffer.put(buffer, "\n")
|
||||||
|
traverse(buffer, do_and_else[:do])
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Buffer.put(buffer, "<%= " <> Macro.to_string(macro) <> " %>")
|
||||||
|
Buffer.put(buffer, "\n")
|
||||||
|
traverse(buffer, do_and_else[:do])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def traverse(buffer, [first | rest]) do
|
||||||
|
traverse(buffer, first)
|
||||||
|
|
||||||
|
traverse(buffer, rest)
|
||||||
|
end
|
||||||
|
|
||||||
|
def traverse(buffer, text) when is_binary(text) do
|
||||||
|
Buffer.put(buffer, text)
|
||||||
|
Buffer.put(buffer, "\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def traverse(_buffer, arg) when arg in [nil, []] do
|
||||||
|
nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
```
|
|
||||||
"""
|
defmacro temple([do: block] = _block) do
|
||||||
defmacro partial(partial) do
|
{:ok, buffer} = Buffer.start_link()
|
||||||
|
|
||||||
|
buffer
|
||||||
|
|> Temple.Private.traverse(block)
|
||||||
|
|
||||||
|
markup = Buffer.get(buffer)
|
||||||
|
|
||||||
|
Buffer.stop(buffer)
|
||||||
|
|
||||||
quote location: :keep do
|
quote location: :keep do
|
||||||
Temple.Utils.put_buffer(
|
unquote(markup)
|
||||||
var!(buff, Temple.Html),
|
|
||||||
unquote(partial) |> Temple.Utils.from_safe()
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
defmacro temple(block) do
|
||||||
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. The whole list or map of assigns can be accessed by a special assign called `@assigns`.
|
|
||||||
|
|
||||||
## 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
|
|
||||||
|
|
||||||
temple do
|
|
||||||
flex id: "my-flex" do
|
|
||||||
div "Item 1"
|
|
||||||
div "Item 2"
|
|
||||||
div "Item 3"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# {:safe, "<div id=\"my-flex\" class=\"flex\">
|
|
||||||
# <div>Item 1</div>
|
|
||||||
# <div>Item 2</div>
|
|
||||||
# <div>Item 3</div>
|
|
||||||
# </div>"}
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
defmacro defcomponent(name, [do: _] = block) do
|
|
||||||
quote location: :keep do
|
quote location: :keep do
|
||||||
defmacro unquote(name)() do
|
import Temple
|
||||||
outer = unquote(Macro.escape(block))
|
|
||||||
|
|
||||||
Temple.Utils.__quote__(outer)
|
{:ok, buffer} = Buffer.start_link()
|
||||||
end
|
|
||||||
|
|
||||||
defmacro unquote(name)(assigns_or_block)
|
buffer
|
||||||
|
|> Temple.Private.traverse(unquote(block))
|
||||||
|
|
||||||
defmacro unquote(name)([{:do, inner}]) do
|
markup = Buffer.get(buffer)
|
||||||
outer =
|
|
||||||
unquote(Macro.escape(block))
|
|
||||||
|> Temple.Utils.__insert_assigns__([], inner)
|
|
||||||
|
|
||||||
Temple.Utils.__quote__(outer)
|
Buffer.stop(buffer)
|
||||||
end
|
|
||||||
|
|
||||||
defmacro unquote(name)(assigns) do
|
markup
|
||||||
outer =
|
|
||||||
unquote(Macro.escape(block))
|
|
||||||
|> Temple.Utils.__insert_assigns__(assigns, nil)
|
|
||||||
|
|
||||||
Temple.Utils.__quote__(outer)
|
|
||||||
end
|
|
||||||
|
|
||||||
defmacro unquote(name)(assigns, inner) do
|
|
||||||
outer =
|
|
||||||
unquote(Macro.escape(block))
|
|
||||||
|> Temple.Utils.__insert_assigns__(assigns, inner)
|
|
||||||
|
|
||||||
Temple.Utils.__quote__(outer)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defmacro live_temple([do: block] = _block) do
|
||||||
|
{:ok, buffer} = Buffer.start_link()
|
||||||
|
|
||||||
|
buffer
|
||||||
|
|> Temple.Private.traverse(block)
|
||||||
|
|
||||||
|
markup = Buffer.get(buffer)
|
||||||
|
|
||||||
|
Buffer.stop(buffer)
|
||||||
|
EEx.compile_string(markup, engine: Phoenix.LiveView.Engine)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,112 +0,0 @@
|
||||||
defmodule Temple.Elements do
|
|
||||||
@moduledoc """
|
|
||||||
This module contains the primitives used to generate the macros in the `Temple.Html` and `Temple.Svg` modules.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Defines an element.
|
|
||||||
|
|
||||||
*Note*: Underscores are converted to dashes.
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
defmodule MyElements do
|
|
||||||
import Temple.Elements
|
|
||||||
|
|
||||||
defelement :super_select, :nonvoid # <super-select></super-select>
|
|
||||||
defelement :super_input, :void # <super-input>
|
|
||||||
end
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
|
|
||||||
defmacro defelement(name, type)
|
|
||||||
|
|
||||||
defmacro defelement(name, :nonvoid) do
|
|
||||||
quote location: :keep do
|
|
||||||
defmacro unquote(name)() do
|
|
||||||
Temple.Elements.nonvoid_element(unquote(name))
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc false
|
|
||||||
defmacro unquote(name)(attrs_or_content_or_block)
|
|
||||||
|
|
||||||
defmacro unquote(name)([{:do, _inner}] = block) do
|
|
||||||
Temple.Elements.nonvoid_element(unquote(name), block)
|
|
||||||
end
|
|
||||||
|
|
||||||
defmacro unquote(name)(attrs_or_content) do
|
|
||||||
Temple.Elements.nonvoid_element(unquote(name), attrs_or_content)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc false
|
|
||||||
defmacro unquote(name)(attrs_or_content, block_or_attrs)
|
|
||||||
|
|
||||||
defmacro unquote(name)(attrs, [{:do, _inner}] = block) do
|
|
||||||
Temple.Elements.nonvoid_element(unquote(name), attrs, block)
|
|
||||||
end
|
|
||||||
|
|
||||||
defmacro unquote(name)(content, attrs) do
|
|
||||||
Temple.Elements.nonvoid_element(unquote(name), content, attrs)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defmacro defelement(name, :void) do
|
|
||||||
quote location: :keep do
|
|
||||||
defmacro unquote(name)(attrs \\ []) do
|
|
||||||
Temple.Elements.void_element(unquote(name), attrs)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc false
|
|
||||||
def nonvoid_element(el) do
|
|
||||||
quote location: :keep do
|
|
||||||
Temple.Utils.put_open_tag(var!(buff, Temple.Html), unquote(el), [])
|
|
||||||
Temple.Utils.put_close_tag(var!(buff, Temple.Html), unquote(el))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc false
|
|
||||||
def nonvoid_element(el, attrs_or_content_or_block)
|
|
||||||
|
|
||||||
def nonvoid_element(el, [{:do, inner}]) do
|
|
||||||
quote location: :keep do
|
|
||||||
Temple.Utils.put_open_tag(var!(buff, Temple.Html), unquote(el), [])
|
|
||||||
_ = unquote(inner)
|
|
||||||
Temple.Utils.put_close_tag(var!(buff, Temple.Html), unquote(el))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def nonvoid_element(el, attrs_or_content) do
|
|
||||||
quote location: :keep do
|
|
||||||
Temple.Utils.put_open_tag(var!(buff, Temple.Html), unquote(el), unquote(attrs_or_content))
|
|
||||||
Temple.Utils.put_close_tag(var!(buff, Temple.Html), unquote(el))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc false
|
|
||||||
def nonvoid_element(el, attrs_or_content, block_or_attrs)
|
|
||||||
|
|
||||||
def nonvoid_element(el, attrs, [{:do, inner}] = _block) do
|
|
||||||
quote location: :keep do
|
|
||||||
Temple.Utils.put_open_tag(var!(buff, Temple.Html), unquote_splicing([el, attrs]))
|
|
||||||
_ = unquote(inner)
|
|
||||||
Temple.Utils.put_close_tag(var!(buff, Temple.Html), unquote(el))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def nonvoid_element(el, content, attrs) do
|
|
||||||
quote location: :keep do
|
|
||||||
Temple.Utils.put_open_tag(var!(buff, Temple.Html), unquote_splicing([el, attrs]))
|
|
||||||
text unquote(content)
|
|
||||||
Temple.Utils.put_close_tag(var!(buff, Temple.Html), unquote(el))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc false
|
|
||||||
def void_element(el, attrs \\ []) do
|
|
||||||
quote location: :keep do
|
|
||||||
Temple.Utils.put_void_tag(var!(buff, Temple.Html), unquote_splicing([el, attrs]))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,117 +1,19 @@
|
||||||
defmodule Temple.Engine do
|
defmodule Temple.Engine do
|
||||||
@behaviour Phoenix.Template.Engine
|
@behaviour Phoenix.Template.Engine
|
||||||
|
|
||||||
@moduledoc """
|
@moduledoc false
|
||||||
Temple provides a templating engine for use in Phoenix web applications.
|
|
||||||
|
|
||||||
You can configure your application to use Temple templates by adding the following configuration.
|
|
||||||
|
|
||||||
```elixir
|
|
||||||
# config.exs
|
|
||||||
config :phoenix, :template_engines, exs: Temple.Engine
|
|
||||||
|
|
||||||
# config/dev.exs
|
|
||||||
config :your_app, YourAppWeb.Endpoint,
|
|
||||||
live_reload: [
|
|
||||||
patterns: [
|
|
||||||
~r"lib/your_app_web/templates/.*(exs)$"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
# your_app_web.ex
|
|
||||||
def view do
|
|
||||||
quote location: :keep do
|
|
||||||
# ...
|
|
||||||
use Temple # Replaces the call to import Phoenix.HTML
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Temple templates use the `.exs` extension, because they are written with pure Elixir!
|
|
||||||
|
|
||||||
`assigns` (@conn, etc) are handled the same as normal `Phoenix.HTML.Engine` templates.
|
|
||||||
|
|
||||||
Note: The `Temple.temple/1` macro is _not_ needed for Temple templates due to the engine taking care of that for you.
|
|
||||||
|
|
||||||
```
|
|
||||||
# app.html.exs
|
|
||||||
html lang: "en" do
|
|
||||||
head do
|
|
||||||
meta charset: "utf-8"
|
|
||||||
meta http_equiv: "X-UA-Compatible", content: "IE=edge"
|
|
||||||
meta name: "viewport", content: "width=device-width, initial-scale=1.0"
|
|
||||||
title "YourApp · Phoenix Framework"
|
|
||||||
|
|
||||||
link rel: "stylesheet", href: Routes.static_path(@conn, "/css/app.css")
|
|
||||||
end
|
|
||||||
|
|
||||||
body do
|
|
||||||
header do
|
|
||||||
section class: "container" do
|
|
||||||
nav role: "navigation" do
|
|
||||||
ul do
|
|
||||||
li do: a("Get Started", href: "https://hexdocs.pm/phoenix/overview.html")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
a href: "http://phoenixframework.org/", class: "phx-logo" do
|
|
||||||
img src: Routes.static_path(@conn, "/images/phoenix.png"),
|
|
||||||
alt: "Phoenix Framework Logo"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
|
|
||||||
def compile(path, _name) do
|
def compile(path, _name) do
|
||||||
template =
|
require Temple
|
||||||
path
|
|
||||||
|> File.read!()
|
|
||||||
|> Code.string_to_quoted!(file: path)
|
|
||||||
|> handle_assigns()
|
|
||||||
|
|
||||||
quote location: :keep do
|
template = path |> File.read!() |> Code.string_to_quoted!(file: path)
|
||||||
use Temple
|
|
||||||
|
|
||||||
temple do: unquote(template)
|
ast =
|
||||||
end
|
quote do
|
||||||
end
|
unquote(template)
|
||||||
|
end
|
||||||
|
|
||||||
defp handle_assigns(quoted) do
|
Temple.temple(ast)
|
||||||
quoted
|
|> EEx.compile_string(engine: Phoenix.HTML.Engine, file: path, line: 1)
|
||||||
|> Macro.prewalk(fn
|
|
||||||
{:@, _, [{key, _, _}]} ->
|
|
||||||
quote location: :keep do
|
|
||||||
case Access.fetch(var!(assigns), unquote(key)) do
|
|
||||||
{:ok, val} ->
|
|
||||||
val
|
|
||||||
|
|
||||||
:error ->
|
|
||||||
raise ArgumentError, """
|
|
||||||
assign @#{unquote(key)} not available in Temple template.
|
|
||||||
Please make sure all proper assigns have been set. If this
|
|
||||||
is a child template, ensure assigns are given explicitly by
|
|
||||||
the parent template as they are not automatically forwarded.
|
|
||||||
Available assigns: #{inspect(Enum.map(var!(assigns), &elem(&1, 0)))}
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
ast ->
|
|
||||||
ast
|
|
||||||
end)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,304 +0,0 @@
|
||||||
defmodule Temple.Form do
|
|
||||||
@moduledoc """
|
|
||||||
This modules wraps all of the functions from the `Phoenix.HTML.Form` module to make them compatible with with Temple.
|
|
||||||
"""
|
|
||||||
|
|
||||||
alias Phoenix.HTML
|
|
||||||
alias Temple.Utils
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Generates an empty form tag.
|
|
||||||
|
|
||||||
See `Temple.Form.form_for/4` for more details
|
|
||||||
"""
|
|
||||||
defmacro form_for(form_data, action) do
|
|
||||||
quote location: :keep do
|
|
||||||
form_for(unquote_splicing([form_data, action]), [])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Generates a form tag with a form builder and a block.
|
|
||||||
|
|
||||||
The form builder will be available inside the block through the `form` variable.
|
|
||||||
|
|
||||||
This is a wrapper around the `Phoenix.HTML.Form.form_for/4` function and accepts all of the same options.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
```
|
|
||||||
temple do
|
|
||||||
form_for @conn, Routes.some_path(@conn, :create) do
|
|
||||||
text_input form, :name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# {:safe,
|
|
||||||
# "<form accept-charset=\"UTF-8\" action=\"/\" method=\"post\">
|
|
||||||
# <input name=\"_csrf_token\" type=\"hidden\" value=\"AS5qfX1gcns6eU56BlQgBlwCDgMlNgAAiJ0MR91Kh3v3bbCS5SKjuw==\">
|
|
||||||
# <input name=\"_utf8\" type=\"hidden\" value=\"✓\">
|
|
||||||
# <input id=\"name\" name=\"name\" type=\"text\">
|
|
||||||
# </form>"}
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
defmacro form_for(form_data, action, opts \\ [], block) do
|
|
||||||
quote location: :keep do
|
|
||||||
var!(form) = HTML.Form.form_for(unquote_splicing([form_data, action, opts]))
|
|
||||||
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), var!(form) |> HTML.Safe.to_iodata())
|
|
||||||
_ = unquote(block)
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), "</form>")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@helpers [
|
|
||||||
:checkbox,
|
|
||||||
:color_input,
|
|
||||||
:date_input,
|
|
||||||
:date_select,
|
|
||||||
:datetime_local_input,
|
|
||||||
:datetime_select,
|
|
||||||
:email_input,
|
|
||||||
:file_input,
|
|
||||||
:hidden_input,
|
|
||||||
:number_input,
|
|
||||||
:password_input,
|
|
||||||
:range_input,
|
|
||||||
:search_input,
|
|
||||||
:telephone_input,
|
|
||||||
:text_input,
|
|
||||||
:time_input,
|
|
||||||
:time_select,
|
|
||||||
:url_input
|
|
||||||
]
|
|
||||||
|
|
||||||
for helper <- @helpers do
|
|
||||||
@doc """
|
|
||||||
Please see `Phoenix.HTML.Form.#{helper}/3` for details.
|
|
||||||
"""
|
|
||||||
defmacro unquote(helper)(form, field, opts \\ []) do
|
|
||||||
helper = unquote(helper)
|
|
||||||
|
|
||||||
quote location: :keep do
|
|
||||||
{:safe, input} =
|
|
||||||
apply(Phoenix.HTML.Form, unquote(helper), [unquote_splicing([form, field, opts])])
|
|
||||||
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), input)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Please see `Phoenix.HTML.Form.textarea/3` for details.
|
|
||||||
|
|
||||||
Note: Temple defines this function as `text_area` with an underscore, whereas Phoenix.HTML defines it as `textarea` without an underscore.
|
|
||||||
"""
|
|
||||||
defmacro text_area(form, field, opts \\ []) do
|
|
||||||
quote location: :keep do
|
|
||||||
{:safe, input} = Phoenix.HTML.Form.textarea(unquote_splicing([form, field, opts]))
|
|
||||||
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), input)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Please see `Phoenix.HTML.Form.reset/2` for details.
|
|
||||||
"""
|
|
||||||
defmacro reset(value, opts \\ []) do
|
|
||||||
quote location: :keep do
|
|
||||||
{:safe, input} = Phoenix.HTML.Form.reset(unquote_splicing([value, opts]))
|
|
||||||
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), input)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Please see `Phoenix.HTML.Form.submit/1` for details.
|
|
||||||
"""
|
|
||||||
defmacro submit(do: block) do
|
|
||||||
quote location: :keep do
|
|
||||||
{:safe, input} = Phoenix.HTML.Form.submit(do: temple(do: unquote(block)))
|
|
||||||
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), input)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defmacro submit(value) do
|
|
||||||
quote location: :keep do
|
|
||||||
{:safe, input} = Phoenix.HTML.Form.submit(unquote(value))
|
|
||||||
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), input)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Please see `Phoenix.HTML.Form.submit/1` for details.
|
|
||||||
"""
|
|
||||||
defmacro submit(opts, do: block) do
|
|
||||||
quote location: :keep do
|
|
||||||
{:safe, input} = Phoenix.HTML.Form.submit(unquote(opts), do: temple(do: unquote(block)))
|
|
||||||
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), input)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defmacro submit(value, opts) do
|
|
||||||
quote location: :keep do
|
|
||||||
{:safe, input} = Phoenix.HTML.Form.submit(unquote_splicing([value, opts]))
|
|
||||||
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), input)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Please see `Phoenix.HTML.Form.label/2` for details.
|
|
||||||
"""
|
|
||||||
defmacro phx_label(form, field) do
|
|
||||||
quote location: :keep do
|
|
||||||
{:safe, input} = Phoenix.HTML.Form.label(unquote_splicing([form, field]))
|
|
||||||
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), input)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Please see `Phoenix.HTML.Form.label/3` for details.
|
|
||||||
"""
|
|
||||||
defmacro phx_label(form, field, do: block) do
|
|
||||||
quote location: :keep do
|
|
||||||
{:safe, input} =
|
|
||||||
Phoenix.HTML.Form.label(unquote_splicing([form, field]), do: temple(do: unquote(block)))
|
|
||||||
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), input)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defmacro phx_label(form, field, text_or_opts) do
|
|
||||||
quote location: :keep do
|
|
||||||
{:safe, input} = Phoenix.HTML.Form.label(unquote_splicing([form, field, text_or_opts]))
|
|
||||||
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), input)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Please see `Phoenix.HTML.Form.label/4` for details.
|
|
||||||
"""
|
|
||||||
defmacro phx_label(form, field, opts, do: block) do
|
|
||||||
quote location: :keep do
|
|
||||||
{:safe, input} =
|
|
||||||
Phoenix.HTML.Form.label(unquote_splicing([form, field, opts]),
|
|
||||||
do: temple(do: unquote(block))
|
|
||||||
)
|
|
||||||
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), input)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defmacro phx_label(form, field, text, opts) do
|
|
||||||
quote location: :keep do
|
|
||||||
{:safe, input} = Phoenix.HTML.Form.label(unquote_splicing([form, field, text, opts]))
|
|
||||||
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), input)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Please see `Phoenix.HTML.Form.radio_button/4` for details.
|
|
||||||
"""
|
|
||||||
defmacro radio_button(form, field, value, attrs \\ []) do
|
|
||||||
quote location: :keep do
|
|
||||||
{:safe, input} =
|
|
||||||
Phoenix.HTML.Form.radio_button(unquote_splicing([form, field, value, attrs]))
|
|
||||||
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), input)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Please see `Phoenix.HTML.Form.multiple_select/4` for details.
|
|
||||||
"""
|
|
||||||
defmacro multiple_select(form, field, options, attrs \\ []) do
|
|
||||||
quote location: :keep do
|
|
||||||
{:safe, input} =
|
|
||||||
Phoenix.HTML.Form.multiple_select(unquote_splicing([form, field, options, attrs]))
|
|
||||||
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), input)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Please see `Phoenix.HTML.Form.select/4` for details.
|
|
||||||
"""
|
|
||||||
defmacro select(form, field, options, attrs \\ []) do
|
|
||||||
quote location: :keep do
|
|
||||||
{:safe, input} = Phoenix.HTML.Form.select(unquote_splicing([form, field, options, attrs]))
|
|
||||||
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), input)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Generate a new form builder for the given parameter in form.
|
|
||||||
|
|
||||||
The form builder will be available inside the block through the `inner_form` variable.
|
|
||||||
|
|
||||||
This is a wrapper around the `Phoenix.HTML.Form.inputs_for/4` function and accepts all of the same options.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
```
|
|
||||||
temple do
|
|
||||||
form_for @parent, Routes.some_path(@conn, :create) do
|
|
||||||
text_input form, :name
|
|
||||||
|
|
||||||
inputs_for form, :job do
|
|
||||||
text_input inner_form, :description
|
|
||||||
end
|
|
||||||
|
|
||||||
inputs_for form, :children do
|
|
||||||
text_input inner_form, :name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# {:safe,
|
|
||||||
# "<form accept-charset=\"UTF-8\" action=\"/\" method=\"post\">
|
|
||||||
# <input name=\"_csrf_token\" type=\"hidden\" value=\"AS5qfX1gcns6eU56BlQgBlwCDgMlNgAAiJ0MR91Kh3v3bbCS5SKjuw==\">
|
|
||||||
# <input name=\"_utf8\" type=\"hidden\" value=\"✓\">
|
|
||||||
# <input id=\"name\" name=\"parent[name]\" type=\"text\">
|
|
||||||
#
|
|
||||||
# <input id=\"name\" name=\"parent[job][description]\" type=\"text\">
|
|
||||||
#
|
|
||||||
# <input id=\"name\" name=\"parent[children][1][name]\" type=\"text\">
|
|
||||||
# <input id=\"name\" name=\"parent[children][2][name]\" type=\"text\">
|
|
||||||
# </form>"}
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
defmacro inputs_for(form, field, options \\ [], do: block) do
|
|
||||||
quote location: :keep do
|
|
||||||
form = unquote(form)
|
|
||||||
field = unquote(field)
|
|
||||||
options = unquote(options)
|
|
||||||
|
|
||||||
options =
|
|
||||||
form.options
|
|
||||||
|> Keyword.take([:multipart])
|
|
||||||
|> Keyword.merge(options)
|
|
||||||
|
|
||||||
form.impl.to_form(form.source, form, field, options)
|
|
||||||
|> Enum.each(fn form ->
|
|
||||||
Enum.map(form.hidden, fn {k, v} ->
|
|
||||||
{:safe, hidden_input} = Phoenix.HTML.Form.hidden_input(form, k, value: v)
|
|
||||||
|
|
||||||
hidden_input
|
|
||||||
end)
|
|
||||||
|> Enum.each(&Utils.put_buffer(var!(buff, Temple.Html), &1))
|
|
||||||
|
|
||||||
var!(inner_form) = form
|
|
||||||
|
|
||||||
_ = unquote(block)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,108 +0,0 @@
|
||||||
defmodule Temple.Html do
|
|
||||||
require Temple.Elements
|
|
||||||
|
|
||||||
@moduledoc """
|
|
||||||
The `Temple.Html` module defines macros for all HTML5 compliant elements.
|
|
||||||
|
|
||||||
`Temple.Html` macros must be called inside of a `Temple.temple/1` block.
|
|
||||||
|
|
||||||
*Note*: Only the lowest arity macros are documented. Void elements are defined as a 1-arity macro and non-void elements are defined as 0, 1, and 2-arity macros.
|
|
||||||
|
|
||||||
## Attributes
|
|
||||||
|
|
||||||
Html accept a keyword list or a map of attributes to be emitted into the element's opening tag. Multi-word attribute keys written in snake_case (`data_url`) will be transformed into kebab-case (`data-url`).
|
|
||||||
|
|
||||||
## Children
|
|
||||||
|
|
||||||
Non-void elements (such as `div`) accept a block that can be used to nest other tags or text nodes. These blocks can contain arbitrary Elixir code such as variables and for comprehensions.
|
|
||||||
|
|
||||||
If you are only emitting a text node within a block, you can use the shortened syntax by passing the text in as the first parameter of the tag.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
```
|
|
||||||
temple do
|
|
||||||
# empty non-void element
|
|
||||||
div()
|
|
||||||
|
|
||||||
# non-void element with keyword list attributes
|
|
||||||
div class: "text-red", id: "my-el"
|
|
||||||
|
|
||||||
# non-void element with map attributes
|
|
||||||
div %{:class => "text-red", "id" => "my-el"}
|
|
||||||
|
|
||||||
# non-void element with children
|
|
||||||
div do
|
|
||||||
text "Hello, world!"
|
|
||||||
|
|
||||||
for name <- @names do
|
|
||||||
div data_name: name
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# non-void element with a single text node
|
|
||||||
div "Hello, world!", class: "text-green"
|
|
||||||
|
|
||||||
# void elements
|
|
||||||
input name: "comments", placeholder: "Enter a comment..."
|
|
||||||
end
|
|
||||||
|
|
||||||
# {:safe,
|
|
||||||
# "<div></div>
|
|
||||||
# <div class=\"text-red\" id=\"my-el\"></div>
|
|
||||||
# <div>
|
|
||||||
# Hello, world!
|
|
||||||
# <div data-name=\"Alice\"></div>
|
|
||||||
# <div data-name=\"Bob\"></div>
|
|
||||||
# <div data-name=\"Carol\"></div>
|
|
||||||
# </div>
|
|
||||||
# <div class=\"text-green\">Hello, world!</div>
|
|
||||||
# <input name=\"comments\" placeholder=\"Enter a comment...\">"
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
"""
|
|
||||||
|
|
||||||
@nonvoid_elements ~w[
|
|
||||||
head title style script
|
|
||||||
noscript template
|
|
||||||
body section nav article aside h1 h2 h3 h4 h5 h6
|
|
||||||
header footer address main
|
|
||||||
p pre blockquote ol ul li dl dt dd figure figcaption div
|
|
||||||
a em strong small s cite q dfn abbr data time code var samp kbd
|
|
||||||
sub sup i b u mark ruby rt rp bdi bdo span
|
|
||||||
ins del
|
|
||||||
iframe object video audio canvas
|
|
||||||
map
|
|
||||||
table caption colgroup tbody thead tfoot tr td th
|
|
||||||
form fieldset legend label button select datalist optgroup
|
|
||||||
option textarea output progress meter
|
|
||||||
details summary menuitem menu
|
|
||||||
]a
|
|
||||||
|
|
||||||
@void_elements ~w[
|
|
||||||
meta link base
|
|
||||||
area br col embed hr img input keygen param source track wbr
|
|
||||||
]a
|
|
||||||
|
|
||||||
@doc false
|
|
||||||
def nonvoid_elements, do: @nonvoid_elements
|
|
||||||
@doc false
|
|
||||||
def void_elements, do: @void_elements
|
|
||||||
|
|
||||||
for el <- @nonvoid_elements do
|
|
||||||
Temple.Elements.defelement(unquote(el), :nonvoid)
|
|
||||||
end
|
|
||||||
|
|
||||||
for el <- @void_elements do
|
|
||||||
Temple.Elements.defelement(unquote(el), :void)
|
|
||||||
end
|
|
||||||
|
|
||||||
defmacro html(attrs \\ [], [{:do, _inner}] = block) do
|
|
||||||
doc_type =
|
|
||||||
quote location: :keep do
|
|
||||||
Temple.Utils.put_buffer(var!(buff, Temple.Html), "<!DOCTYPE html>")
|
|
||||||
end
|
|
||||||
|
|
||||||
[doc_type, Temple.Elements.nonvoid_element(:html, attrs, block)]
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,76 +0,0 @@
|
||||||
defmodule Temple.HtmlToTemple do
|
|
||||||
@moduledoc false
|
|
||||||
|
|
||||||
@tags Temple.Html.void_elements() ++
|
|
||||||
Temple.Html.nonvoid_elements() ++ Temple.Svg.elements() ++ [:html]
|
|
||||||
|
|
||||||
def parse(doc) do
|
|
||||||
result =
|
|
||||||
doc
|
|
||||||
|> Floki.parse()
|
|
||||||
|> List.wrap()
|
|
||||||
|> Enum.map(&do_parse(&1, 0))
|
|
||||||
|> Enum.join("\n")
|
|
||||||
|
|
||||||
{:ok, result}
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_parse({tag, [], []}, indent) do
|
|
||||||
tag = tag |> find_tag
|
|
||||||
(Temple.Utils.kebab_to_snake(tag) <> "()\n") |> pad_indent(indent)
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_parse({tag, attrs, []}, indent) do
|
|
||||||
tag = tag |> find_tag
|
|
||||||
(Temple.Utils.kebab_to_snake(tag) <> build_attrs(attrs) <> "\n") |> pad_indent(indent)
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_parse({tag, attrs, [""]}, indent), do: do_parse({tag, attrs, []}, indent)
|
|
||||||
|
|
||||||
def do_parse({tag, attrs, children}, indent) do
|
|
||||||
tag = tag |> find_tag
|
|
||||||
|
|
||||||
head =
|
|
||||||
(Temple.Utils.kebab_to_snake(tag) <> build_attrs(attrs) <> " do\n")
|
|
||||||
|> pad_indent(indent)
|
|
||||||
|
|
||||||
parsed_childs =
|
|
||||||
for child <- children do
|
|
||||||
do_parse(child, indent + 1)
|
|
||||||
end
|
|
||||||
|> Enum.join("\n")
|
|
||||||
|
|
||||||
head <> parsed_childs <> pad_indent("end\n", indent)
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_parse(text, indent) when is_binary(text) do
|
|
||||||
(~s|text "| <> text <> ~s|"\n|) |> pad_indent(indent)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp build_attrs([]), do: ""
|
|
||||||
|
|
||||||
defp build_attrs(attrs) do
|
|
||||||
attrs =
|
|
||||||
for {key, value} <- attrs do
|
|
||||||
wrap_in_quotes(key) <> ~s|: "| <> value <> ~s|"|
|
|
||||||
end
|
|
||||||
|> Enum.join(", ")
|
|
||||||
|
|
||||||
" " <> attrs
|
|
||||||
end
|
|
||||||
|
|
||||||
defp wrap_in_quotes(key) do
|
|
||||||
if Regex.match?(~r/[^a-zA-Z_]/, key) do
|
|
||||||
~s|"| <> key <> ~s|"|
|
|
||||||
else
|
|
||||||
key
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp pad_indent(paddable, indent) do
|
|
||||||
String.pad_leading(paddable, 2 * indent + String.length(paddable))
|
|
||||||
end
|
|
||||||
|
|
||||||
defp find_tag(tag),
|
|
||||||
do: @tags |> Enum.find(fn x -> String.downcase(to_string(x)) == tag end)
|
|
||||||
end
|
|
|
@ -1,50 +0,0 @@
|
||||||
defmodule Temple.Link do
|
|
||||||
alias Phoenix.HTML
|
|
||||||
alias Temple.Utils
|
|
||||||
|
|
||||||
@moduledoc """
|
|
||||||
This modules wraps all of the functions from the `Phoenix.HTML.Link` module to make them compatible with with Temple.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Please see `Phoenix.HTML.Link.link/2` for details.
|
|
||||||
"""
|
|
||||||
defmacro phx_link(opts, do: block) do
|
|
||||||
quote location: :keep do
|
|
||||||
{:safe, link} =
|
|
||||||
temple(do: unquote(block))
|
|
||||||
|> HTML.Link.link(unquote(opts))
|
|
||||||
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), link)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defmacro phx_link(content, opts) do
|
|
||||||
quote location: :keep do
|
|
||||||
{:safe, link} = HTML.Link.link(unquote_splicing([content, opts]))
|
|
||||||
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), link)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Please see `Phoenix.HTML.Link.button/2` for details.
|
|
||||||
"""
|
|
||||||
defmacro phx_button(opts, do: block) do
|
|
||||||
quote location: :keep do
|
|
||||||
{:safe, link} =
|
|
||||||
temple(do: unquote(block))
|
|
||||||
|> HTML.Link.button(unquote(opts))
|
|
||||||
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), link)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defmacro phx_button(content, opts) do
|
|
||||||
quote location: :keep do
|
|
||||||
{:safe, link} = HTML.Link.button(unquote_splicing([content, opts]))
|
|
||||||
|
|
||||||
Utils.put_buffer(var!(buff, Temple.Html), link)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
defmodule Temple.LiveViewEngine do
|
||||||
|
@behaviour Phoenix.Template.Engine
|
||||||
|
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
def compile(path, _name) do
|
||||||
|
require Temple
|
||||||
|
|
||||||
|
ast = path |> File.read!() |> Code.string_to_quoted!(file: path)
|
||||||
|
|
||||||
|
Temple.temple(ast)
|
||||||
|
|> EEx.compile_string(engine: Phoenix.LiveView.Engine, file: path, line: 1)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,27 +0,0 @@
|
||||||
defmodule Temple.Svg do
|
|
||||||
require Temple.Elements
|
|
||||||
|
|
||||||
@moduledoc """
|
|
||||||
The `Temple.Svg` module defines macros for all SVG elements.
|
|
||||||
|
|
||||||
Usage is the same as `Temple.Html`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@elements ~w[
|
|
||||||
animate animateMotion animateTransform circle clipPath
|
|
||||||
color_profile defs desc discard ellipse feBlend
|
|
||||||
feColorMatrix feComponentTransfer feComposite feConvolveMatrix feDiffuseLighting feDisplacementMap feDistantLight feDropShadow
|
|
||||||
feFlood feFuncA feFuncB feFuncG feFuncR feGaussianBlur feImage feMerge feMergeNode feMorphology feOffset
|
|
||||||
fePointLight feSpecularLighting feSpotLight feTile feTurbulence filter foreignObject g hatch hatchpath image line linearGradient
|
|
||||||
marker mask metadata mpath path pattern polygon
|
|
||||||
polyline radialGradient rect set solidcolor stop svg switch symbol text_
|
|
||||||
textPath tspan use view
|
|
||||||
]a
|
|
||||||
|
|
||||||
@doc false
|
|
||||||
def elements(), do: @elements
|
|
||||||
|
|
||||||
for el <- @elements do
|
|
||||||
Temple.Elements.defelement(unquote(el), :nonvoid)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,100 +1,8 @@
|
||||||
defmodule Temple.Utils do
|
defmodule Temple.Utils do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
|
def puts(binary) do
|
||||||
|
IO.puts(binary)
|
||||||
|
|
||||||
def put_open_tag(buff, el, attrs) when is_list(attrs) or is_map(attrs) do
|
binary
|
||||||
el = el |> snake_to_kebab
|
|
||||||
|
|
||||||
put_buffer(buff, "<#{el}#{compile_attrs(attrs)}>")
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def put_open_tag(buff, el, content)
|
|
||||||
when is_binary(content) or is_number(content) or is_atom(content) do
|
|
||||||
el = el |> snake_to_kebab
|
|
||||||
|
|
||||||
put_buffer(buff, "<#{el}>")
|
|
||||||
put_buffer(buff, escape_content(content))
|
|
||||||
end
|
|
||||||
|
|
||||||
def put_close_tag(buff, el) do
|
|
||||||
el = el |> snake_to_kebab
|
|
||||||
|
|
||||||
put_buffer(buff, "</#{el}>")
|
|
||||||
end
|
|
||||||
|
|
||||||
def put_void_tag(buff, el, attrs) do
|
|
||||||
el = el |> snake_to_kebab
|
|
||||||
|
|
||||||
put_buffer(buff, "<#{el}#{Temple.Utils.compile_attrs(attrs)}>")
|
|
||||||
end
|
|
||||||
|
|
||||||
def from_safe({:safe, partial}) do
|
|
||||||
partial
|
|
||||||
end
|
|
||||||
|
|
||||||
def from_safe(partial) do
|
|
||||||
partial |> Phoenix.HTML.html_escape() |> Phoenix.HTML.safe_to_string()
|
|
||||||
end
|
|
||||||
|
|
||||||
def insert_assigns({:@, _, [{:children, _, _}]}, _, inner) do
|
|
||||||
inner
|
|
||||||
end
|
|
||||||
|
|
||||||
def insert_assigns({:@, _, [{:assigns, _, _}]}, assigns, _) do
|
|
||||||
assigns
|
|
||||||
end
|
|
||||||
|
|
||||||
def insert_assigns({:@, _, [{name, _, _}]}, assigns, _) when is_atom(name) do
|
|
||||||
quote location: :keep do
|
|
||||||
Access.get(unquote_splicing([assigns, name]))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def insert_assigns(ast, _, _), do: ast
|
|
||||||
|
|
||||||
def compile_attrs([]), do: ""
|
|
||||||
|
|
||||||
def compile_attrs(attrs) do
|
|
||||||
for {name, value} <- attrs, into: "" do
|
|
||||||
name = snake_to_kebab(name)
|
|
||||||
|
|
||||||
" " <> name <> "=\"" <> to_string(value) <> "\""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def join_and_escape(markup) do
|
|
||||||
markup |> Enum.reverse() |> Enum.join("\n") |> Phoenix.HTML.raw()
|
|
||||||
end
|
|
||||||
|
|
||||||
def start_buffer(initial_buffer), do: Agent.start(fn -> initial_buffer end)
|
|
||||||
def put_buffer(buff, content), do: Agent.update(buff, &[content | &1])
|
|
||||||
def get_buffer(buff), do: Agent.get(buff, & &1)
|
|
||||||
def stop_buffer(buff), do: Agent.stop(buff)
|
|
||||||
|
|
||||||
def escape_content(content) do
|
|
||||||
content
|
|
||||||
|> to_string
|
|
||||||
|> Phoenix.HTML.html_escape()
|
|
||||||
|> Phoenix.HTML.safe_to_string()
|
|
||||||
end
|
|
||||||
|
|
||||||
defp snake_to_kebab(stringable),
|
|
||||||
do: stringable |> to_string() |> String.replace_trailing("_", "") |> String.replace("_", "-")
|
|
||||||
|
|
||||||
def kebab_to_snake(stringable),
|
|
||||||
do: stringable |> to_string() |> String.replace("-", "_")
|
|
||||||
|
|
||||||
def __quote__(outer) do
|
|
||||||
quote [location: :keep], do: unquote(outer)
|
|
||||||
end
|
|
||||||
|
|
||||||
def __insert_assigns__(block, assigns, inner) do
|
|
||||||
block
|
|
||||||
|> Macro.prewalk(&Temple.Utils.insert_assigns(&1, assigns, inner))
|
|
||||||
end
|
|
||||||
|
|
||||||
def doc_path(:html, el), do: "./tmp/docs/html/#{el}.txt"
|
|
||||||
def doc_path(:svg, el), do: "./tmp/docs/svg/#{el}.txt"
|
|
||||||
|
|
||||||
def to_valid_tag(tag),
|
|
||||||
do: tag |> to_string |> String.replace_trailing("_", "") |> String.replace("_", "-")
|
|
||||||
end
|
end
|
||||||
|
|
11
mix.exs
11
mix.exs
|
@ -6,7 +6,7 @@ defmodule Temple.MixProject do
|
||||||
app: :temple,
|
app: :temple,
|
||||||
name: "Temple",
|
name: "Temple",
|
||||||
description: "An HTML DSL for Elixir and Phoenix",
|
description: "An HTML DSL for Elixir and Phoenix",
|
||||||
version: "0.5.0",
|
version: "0.6.0-alpha.0",
|
||||||
package: package(),
|
package: package(),
|
||||||
elixirc_paths: elixirc_paths(Mix.env()),
|
elixirc_paths: elixirc_paths(Mix.env()),
|
||||||
elixir: "~> 1.7",
|
elixir: "~> 1.7",
|
||||||
|
@ -46,13 +46,8 @@ defmodule Temple.MixProject do
|
||||||
|
|
||||||
defp deps do
|
defp deps do
|
||||||
[
|
[
|
||||||
{:phoenix_html, "~> 2.13"},
|
{:ex_doc, "~> 0.22.0", only: [:dev], runtime: false},
|
||||||
{:ecto, "~> 3.0", optional: true},
|
{:phoenix, ">= 0.0.0", optional: true}
|
||||||
{:phoenix_ecto, "~> 4.0", optional: true},
|
|
||||||
{:ex_doc, "~> 0.0", only: [:dev], runtime: false},
|
|
||||||
{:html_sanitize_ex, "~> 1.3", only: [:dev, :test], runtime: false},
|
|
||||||
{:phoenix, "~> 1.4", optional: true},
|
|
||||||
{:floki, "~> 0.26.0", only: [:dev, :test], runtime: false}
|
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
25
mix.lock
25
mix.lock
|
@ -1,21 +1,14 @@
|
||||||
%{
|
%{
|
||||||
"decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
|
"earmark": {:hex, :earmark, "1.4.5", "62ffd3bd7722fb7a7b1ecd2419ea0b458c356e7168c1f5d65caf09b4fbdd13c8", [:mix], [], "hexpm", "b7d0e6263d83dc27141a523467799a685965bf8b13b6743413f19a7079843f4f"},
|
||||||
"earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
|
"ex_doc": {:hex, :ex_doc, "0.22.1", "9bb6d51508778193a4ea90fa16eac47f8b67934f33f8271d5e1edec2dc0eee4c", [:mix], [{:earmark, "~> 1.4.0", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "d957de1b75cb9f78d3ee17820733dc4460114d8b1e11f7ee4fd6546e69b1db60"},
|
||||||
"ecto": {:hex, :ecto, "3.4.1", "ca5b5f6314eebd7fa2e52c6d78abb1ef955005dd60cc7a047b963ee23ee14a6c", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "748a317a2eacac0b7b6540cb7d2198b79457ede9cec2b4d1582117f90ac309d5"},
|
"makeup": {:hex, :makeup, "1.0.3", "e339e2f766d12e7260e6672dd4047405963c5ec99661abdc432e6ec67d29ef95", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "2e9b4996d11832947731f7608fed7ad2f9443011b3b479ae288011265cdd3dad"},
|
||||||
"ex_doc": {:hex, :ex_doc, "0.21.3", "857ec876b35a587c5d9148a2512e952e24c24345552259464b98bfbb883c7b42", [:mix], [{:earmark, "~> 1.4", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "0db1ee8d1547ab4877c5b5dffc6604ef9454e189928d5ba8967d4a58a801f161"},
|
"makeup_elixir": {:hex, :makeup_elixir, "0.14.1", "4f0e96847c63c17841d42c08107405a005a2680eb9c7ccadfd757bd31dabccfb", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f2438b1a80eaec9ede832b5c41cd4f373b38fd7aa33e3b22d9db79e640cbde11"},
|
||||||
"floki": {:hex, :floki, "0.26.0", "4df88977e2e357c6720e1b650f613444bfb48c5acfc6a0c646ab007d08ad13bf", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "e7b66ce7feef5518a9cd9fc7b52dd62a64028bd9cb6d6ad282a0f0fc90a4ae52"},
|
|
||||||
"html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"},
|
|
||||||
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.4.0", "0310d27d7bafb662f30bff22ec732a72414799c83eaf44239781fd23b96216c0", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm", "c5d79626be0b6e50c19ecdfb783ee26e85bd3a77436b488379ce6dc104ec4593"},
|
|
||||||
"makeup": {:hex, :makeup, "1.0.1", "82f332e461dc6c79dbd82fbe2a9c10d48ed07146f0a478286e590c83c52010b5", [], [{:nimble_parsec, "~> 0.5.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "49736fe5b66a08d8575bf5321d716bac5da20c8e6b97714fec2bcd6febcfa1f8"},
|
|
||||||
"makeup_elixir": {:hex, :makeup_elixir, "0.14.0", "cf8b7c66ad1cff4c14679698d532f0b5d45a3968ffbcbfd590339cb57742f1ae", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "d4b316c7222a85bbaa2fd7c6e90e37e953257ad196dc229505137c5e505e9eff"},
|
|
||||||
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"},
|
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"},
|
||||||
"mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm", "b93e2b1e564bdbadfecc297277f9e6d0902da645b417d6c9210f6038ac63489a"},
|
"nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"},
|
||||||
"nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
|
"phoenix": {:hex, :phoenix, "1.5.3", "bfe0404e48ea03dfe17f141eff34e1e058a23f15f109885bbdcf62be303b49ff", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8e16febeb9640d8b33895a691a56481464b82836d338bb3a23125cd7b6157c25"},
|
||||||
"phoenix": {:hex, :phoenix, "1.4.16", "2cbbe0c81e6601567c44cc380c33aa42a1372ac1426e3de3d93ac448a7ec4308", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "856cc1a032fa53822737413cf51aa60e750525d7ece7d1c0576d90d7c0f05c24"},
|
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
|
||||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"},
|
"plug": {:hex, :plug, "1.10.3", "c9cebe917637d8db0e759039cc106adca069874e1a9034fd6e3fdd427fd3c283", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "01f9037a2a1de1d633b5a881101e6a444bcabb1d386ca1e00bb273a1f1d9d939"},
|
||||||
"phoenix_html": {:hex, :phoenix_html, "2.14.1", "7dabafadedb552db142aacbd1f11de1c0bbaa247f90c449ca549d5e30bbc66b4", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "536d5200ad37fecfe55b3241d90b7a8c3a2ca60cd012fc065f776324fa9ab0a9"},
|
|
||||||
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm", "1f13f9f0f3e769a667a6b6828d29dec37497a082d195cc52dbef401a9b69bf38"},
|
|
||||||
"plug": {:hex, :plug, "1.10.0", "6508295cbeb4c654860845fb95260737e4a8838d34d115ad76cd487584e2fc4d", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "422a9727e667be1bf5ab1de03be6fa0ad67b775b2d84ed908f3264415ef29d4a"},
|
|
||||||
"plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},
|
"plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},
|
||||||
|
"poison": {:hex, :poison, "1.5.2", "560bdfb7449e3ddd23a096929fb9fc2122f709bcc758b2d5d5a5c7d0ea848910", [:mix], [], "hexpm", "4afc59dcadf71be7edc8b934b39f554ec7b31e2b1b1a4767383a663f86958ce3"},
|
||||||
"telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
|
"telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
h1 "Edit <%= schema.human_singular %>"
|
h1 do: "Edit <%= schema.human_singular %>"
|
||||||
|
|
||||||
partial render("form.html", Map.put(assigns, :action, Routes.<%= schema.route_helper %>_path(@conn, :update, @<%= schema.singular %>)))
|
render("form.html", Map.put(assigns, :action, Routes.<%= schema.route_helper %>_path(@conn, :update, @<%= schema.singular %>)))
|
||||||
|
|
||||||
span do
|
span do
|
||||||
phx_link "Back", to: Routes.<%= schema.route_helper %>_path(@conn, :index)
|
link "Back", to: Routes.<%= schema.route_helper %>_path(@conn, :index)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
form_for @changeset, @action do
|
form_for @changeset, @action, fn f ->
|
||||||
if @changeset.action do
|
if @changeset.action do
|
||||||
div class: "alert alert-danger" do
|
div class: "alert alert-danger" do
|
||||||
p "Oops, something went wrong! Please check the errors below."
|
p do: "Oops, something went wrong! Please check the errors below."
|
||||||
end
|
end
|
||||||
end <%= for {label, input, error} <- inputs, input do %>
|
end <%= for {label, input, error} <- inputs, input do %>
|
||||||
<%= label %>
|
<%= label %>
|
||||||
<%= input %>
|
<%= input %>
|
||||||
<%= error %> <% end %>
|
<%= error %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
div do
|
div do
|
||||||
submit "Save"
|
submit "Save"
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
h1 "Listing <%= schema.human_plural %>"
|
h1 do: "Listing <%= schema.human_plural %>"
|
||||||
|
|
||||||
table do
|
table do
|
||||||
thead do
|
thead do
|
||||||
tr do <%= for {k, _} <- schema.attrs do %>
|
tr do <%= for {k, _} <- schema.attrs do %>
|
||||||
th "<%= Phoenix.Naming.humanize(Atom.to_string(k)) %>"<% end %>
|
th do: "<%= Phoenix.Naming.humanize(Atom.to_string(k)) %>"<% end %>
|
||||||
th()
|
th()
|
||||||
end
|
end
|
||||||
tbody do
|
tbody do
|
||||||
for <%= schema.singular %> <- @<%= schema.plural %> do
|
for <%= schema.singular %> <- @<%= schema.plural %> do
|
||||||
tr do <%= for {k, _} <- schema.attrs do %>
|
tr do <%= for {k, _} <- schema.attrs do %>
|
||||||
td <%= schema.singular %>.<%= k %> <% end %>
|
td do: <%= schema.singular %>.<%= k %> <% end %>
|
||||||
td do
|
td do
|
||||||
phx_link "Show", to: Routes.<%= schema.route_helper %>_path(@conn, :show, <%= schema.singular %>)
|
link "Show", to: Routes.<%= schema.route_helper %>_path(@conn, :show, <%= schema.singular %>)
|
||||||
phx_link "Edit", to: Routes.<%= schema.route_helper %>_path(@conn, :edit, <%= schema.singular %>)
|
link "Edit", to: Routes.<%= schema.route_helper %>_path(@conn, :edit, <%= schema.singular %>)
|
||||||
phx_link "Delete", to: Routes.<%= schema.route_helper %>_path(@conn, :delete, <%= schema.singular %>),
|
link "Delete", to: Routes.<%= schema.route_helper %>_path(@conn, :delete, <%= schema.singular %>),
|
||||||
method: :delete, data: [confirm: "Are you sure?"]
|
method: :delete, data: [confirm: "Are you sure?"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -23,5 +23,5 @@ table do
|
||||||
end
|
end
|
||||||
|
|
||||||
span do
|
span do
|
||||||
phx_link "New <%= schema.human_singular %>", to: Routes.<%= schema.route_helper %>_path(@conn, :new)
|
link "New <%= schema.human_singular %>", to: Routes.<%= schema.route_helper %>_path(@conn, :new)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
h1 "New <%= schema.human_singular %>"
|
h1 do: "New <%= schema.human_singular %>"
|
||||||
|
|
||||||
partial render("form.html", Map.put(assigns, :action, Routes.<%= schema.route_helper %>_path(@conn, :create)))
|
render("form.html", Map.put(assigns, :action, Routes.<%= schema.route_helper %>_path(@conn, :create)))
|
||||||
|
|
||||||
span do
|
span do
|
||||||
phx_link "Back", to: Routes.<%= schema.route_helper %>_path(@conn, :index)
|
link "Back", to: Routes.<%= schema.route_helper %>_path(@conn, :index)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
h1 "Show <%= schema.human_singular %>"
|
h1 do: "Show <%= schema.human_singular %>"
|
||||||
|
|
||||||
ul do <%= for {k, _} <- schema.attrs do %>
|
ul do <%= for {k, _} <- schema.attrs do %>
|
||||||
li do
|
li do
|
||||||
strong "<%= Phoenix.Naming.humanize(Atom.to_string(k)) %>"
|
strong do: "<%= Phoenix.Naming.humanize(Atom.to_string(k)) %>"
|
||||||
text @<%= schema.singular %>.<%= k %>
|
@<%= schema.singular %>.<%= k %>
|
||||||
end <% end %>
|
end <% end %>
|
||||||
|
|
||||||
span do
|
span do
|
||||||
phx_link "Edit", to: Routes.<%= schema.route_helper %>_path(@conn, :edit, @<%= schema.singular %>)
|
link "Edit", to: Routes.<%= schema.route_helper %>_path(@conn, :edit, @<%= schema.singular %>)
|
||||||
end
|
end
|
||||||
|
|
||||||
span do
|
span do
|
||||||
phx_link "Back", to: Routes.<%= schema.route_helper %>_path(@conn, :index)
|
link "Back", to: Routes.<%= schema.route_helper %>_path(@conn, :index)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -28,7 +28,7 @@ html lang: "en" do
|
||||||
p get_flash(@conn, :info), class: "alert alert-info", role: "alert"
|
p get_flash(@conn, :info), class: "alert alert-info", role: "alert"
|
||||||
p get_flash(@conn, :error), class: "alert alert-danger", role: "alert"
|
p get_flash(@conn, :error), class: "alert alert-danger", role: "alert"
|
||||||
|
|
||||||
partial render(@view_module, @view_template, assigns)
|
@inner_content
|
||||||
end
|
end
|
||||||
|
|
||||||
script type: "text/javascript", src: Routes.static_path(@conn, "/js/app.js")
|
script type: "text/javascript", src: Routes.static_path(@conn, "/js/app.js")
|
||||||
|
|
|
@ -1,144 +0,0 @@
|
||||||
defmodule Mix.Tasks.HtmlToTempleTest do
|
|
||||||
use ExUnit.Case, async: true
|
|
||||||
|
|
||||||
test "converts html to temple syntax" do
|
|
||||||
html = """
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta>
|
|
||||||
<script></script>
|
|
||||||
<link>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header class="header" data-action="do a thing">
|
|
||||||
<nav role="navigation">
|
|
||||||
<ul>
|
|
||||||
<li><a href="/home">Home</a></li>
|
|
||||||
<li><a href="/about">About</a></li>
|
|
||||||
<li><a href="/profile">Profile</a></li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main role="main">
|
|
||||||
<svg>
|
|
||||||
<path d="alksdjfalksdjfslkadfj"/>
|
|
||||||
<linearGradient>
|
|
||||||
<stop></stop>
|
|
||||||
</linearGradient
|
|
||||||
</svg>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<footer></footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
{:ok, result} = Temple.HtmlToTemple.parse(html)
|
|
||||||
|
|
||||||
assert result === """
|
|
||||||
html lang: "en" do
|
|
||||||
head do
|
|
||||||
meta()
|
|
||||||
|
|
||||||
script()
|
|
||||||
|
|
||||||
link()
|
|
||||||
end
|
|
||||||
|
|
||||||
body do
|
|
||||||
header class: "header", "data-action": "do a thing" do
|
|
||||||
nav role: "navigation" do
|
|
||||||
ul do
|
|
||||||
li do
|
|
||||||
a href: "/home" do
|
|
||||||
text "Home"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
li do
|
|
||||||
a href: "/about" do
|
|
||||||
text "About"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
li do
|
|
||||||
a href: "/profile" do
|
|
||||||
text "Profile"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
main role: "main" do
|
|
||||||
svg do
|
|
||||||
path d: "alksdjfalksdjfslkadfj"
|
|
||||||
|
|
||||||
linearGradient do
|
|
||||||
stop()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
footer()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
|
|
||||||
test "parses HTML fragments" do
|
|
||||||
html = """
|
|
||||||
<section class="phx-hero">
|
|
||||||
<h1><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h1>
|
|
||||||
<p>A productive web framework that<br/>
|
|
||||||
does not compromise speed or maintainability.</p>
|
|
||||||
</section>
|
|
||||||
<section class="row">
|
|
||||||
<article class="column">
|
|
||||||
<h2>Resources</h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="https://hexdocs.pm/phoenix/overview.html">Guides & Docs</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</article>
|
|
||||||
</section>
|
|
||||||
"""
|
|
||||||
|
|
||||||
{:ok, result} = Temple.HtmlToTemple.parse(html)
|
|
||||||
|
|
||||||
assert result === """
|
|
||||||
section class: "phx-hero" do
|
|
||||||
h1 do
|
|
||||||
text "<%= gettext \"Welcome to %{name}!\", name: \"Phoenix\" %>"
|
|
||||||
end
|
|
||||||
|
|
||||||
p do
|
|
||||||
text "A productive web framework that"
|
|
||||||
|
|
||||||
br()
|
|
||||||
|
|
||||||
text "
|
|
||||||
does not compromise speed or maintainability."
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
section class: "row" do
|
|
||||||
article class: "column" do
|
|
||||||
h2 do
|
|
||||||
text "Resources"
|
|
||||||
end
|
|
||||||
|
|
||||||
ul do
|
|
||||||
li do
|
|
||||||
a href: "https://hexdocs.pm/phoenix/overview.html" do
|
|
||||||
text "Guides & Docs"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,71 +0,0 @@
|
||||||
defmodule Temple.Support.Component do
|
|
||||||
import Temple
|
|
||||||
|
|
||||||
defcomponent :flex do
|
|
||||||
div(class: "flex")
|
|
||||||
end
|
|
||||||
|
|
||||||
defcomponent :takes_children do
|
|
||||||
div do
|
|
||||||
div(id: "static-child-1")
|
|
||||||
@children
|
|
||||||
div(id: "static-child-2")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defcomponent :lists_assigns do
|
|
||||||
partial inspect(@assigns) |> Phoenix.HTML.raw()
|
|
||||||
end
|
|
||||||
|
|
||||||
defcomponent :arbitrary_code do
|
|
||||||
num = 1..10 |> Enum.reduce(0, fn x, sum -> x + sum end)
|
|
||||||
|
|
||||||
div do
|
|
||||||
text(num)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defcomponent :uses_conditionals do
|
|
||||||
if @condition do
|
|
||||||
div()
|
|
||||||
else
|
|
||||||
span()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defcomponent :arbitrary_data do
|
|
||||||
for item <- @lists do
|
|
||||||
div do
|
|
||||||
text(inspect(item))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defcomponent :safe do
|
|
||||||
div()
|
|
||||||
end
|
|
||||||
|
|
||||||
defcomponent :safe_with_prop do
|
|
||||||
div id: "safe-with-prop" do
|
|
||||||
text(@prop)
|
|
||||||
|
|
||||||
div do
|
|
||||||
span do
|
|
||||||
for x <- @lists do
|
|
||||||
div(do: text(x))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defcomponent :variable_as_prop do
|
|
||||||
div id: @bob
|
|
||||||
end
|
|
||||||
|
|
||||||
defcomponent :variable_as_prop_with_block do
|
|
||||||
div id: @bob do
|
|
||||||
@children
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -7,16 +7,16 @@ defmodule Temple.Support.Utils do
|
||||||
end
|
end
|
||||||
|
|
||||||
def a == b when is_binary(a) and is_binary(b) do
|
def a == b when is_binary(a) and is_binary(b) do
|
||||||
Kernel.==(
|
a = String.replace(a, "\n", "")
|
||||||
String.replace(a, ~r/\n/, ""),
|
b = String.replace(b, "\n", "")
|
||||||
String.replace(b, ~r/\n/, "")
|
|
||||||
)
|
Kernel.==(a, b)
|
||||||
end
|
end
|
||||||
|
|
||||||
def a =~ b when is_binary(a) and is_binary(b) do
|
def a =~ b when is_binary(a) and is_binary(b) do
|
||||||
Kernel.=~(
|
a = String.replace(a, "\n", "")
|
||||||
String.replace(a, ~r/\n/, ""),
|
b = String.replace(b, "\n", "")
|
||||||
String.replace(b, ~r/\n/, "")
|
|
||||||
)
|
Kernel.=~(a, b)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
defmodule Temple.ElementsTest do
|
|
||||||
use ExUnit.Case, async: true
|
|
||||||
import Temple.Elements, only: [defelement: 2]
|
|
||||||
import Temple, only: [temple: 1, text: 1]
|
|
||||||
import Temple.Html, only: [option: 2]
|
|
||||||
use Temple.Support.Utils
|
|
||||||
|
|
||||||
defelement(:my_select, :nonvoid)
|
|
||||||
defelement(:my_input, :void)
|
|
||||||
|
|
||||||
test "defines a nonvoid element" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
my_select class: "hello" do
|
|
||||||
option "A", value: "A"
|
|
||||||
option "B", value: "B"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result ==
|
|
||||||
~s{<my-select class="hello"><option value="A">A</option><option value="B">B</option></my-select>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "defines a void element" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
my_input(class: "hello")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<my-input class="hello">}
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,714 +0,0 @@
|
||||||
defmodule Temple.FormTest do
|
|
||||||
use ExUnit.Case, async: true
|
|
||||||
use Temple
|
|
||||||
use Temple.Support.Utils
|
|
||||||
|
|
||||||
describe "form_for" do
|
|
||||||
test "returns a form tag" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for(conn, action, [])
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<form}
|
|
||||||
assert result =~ ~s{</form>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "can take a block" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
div()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<form}
|
|
||||||
assert result =~ ~s{<div></div>}
|
|
||||||
assert result =~ ~s{</form>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "can take a block that references the form" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
text_input(form, :bob)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<form}
|
|
||||||
assert result =~ ~s{<input}
|
|
||||||
assert result =~ ~s{type="text"}
|
|
||||||
assert result =~ ~s{name="bob"}
|
|
||||||
assert result =~ ~s{</form>}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defmodule Person do
|
|
||||||
use Ecto.Schema
|
|
||||||
|
|
||||||
schema "persons" do
|
|
||||||
field(:name)
|
|
||||||
belongs_to(:company, Company)
|
|
||||||
has_many(:responsibilities, Reponsibility)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defmodule Company do
|
|
||||||
use Ecto.Schema
|
|
||||||
|
|
||||||
schema "companies" do
|
|
||||||
field(:name)
|
|
||||||
field(:field)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defmodule Responsibility do
|
|
||||||
use Ecto.Schema
|
|
||||||
|
|
||||||
schema "responsibilities" do
|
|
||||||
field(:description)
|
|
||||||
|
|
||||||
belongs_to(:person, Person)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "inputs_for" do
|
|
||||||
test "generates inputs for belongs_to" do
|
|
||||||
person = %Person{company: %Company{}}
|
|
||||||
changeset = Ecto.Changeset.change(person)
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for changeset, action, opts do
|
|
||||||
text_input(form, :name)
|
|
||||||
|
|
||||||
inputs_for form, :company do
|
|
||||||
text_input(inner_form, :name)
|
|
||||||
_ = "Bob"
|
|
||||||
text_input(inner_form, :field)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<form}
|
|
||||||
assert result =~ ~s{<input}
|
|
||||||
assert result =~ ~s{type="text"}
|
|
||||||
assert result =~ ~s{name="person[company][name]"}
|
|
||||||
assert result =~ ~s{name="person[company][field]"}
|
|
||||||
assert result =~ ~s{</form>}
|
|
||||||
refute result =~ ~s{Bob}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates inputs for has_many" do
|
|
||||||
person = %Person{
|
|
||||||
id: 1,
|
|
||||||
responsibilities: [
|
|
||||||
%Responsibility{id: 1, person_id: 1},
|
|
||||||
%Responsibility{id: 2, person_id: 1}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
changeset = Ecto.Changeset.change(person)
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for changeset, action, opts do
|
|
||||||
text_input(form, :name)
|
|
||||||
|
|
||||||
inputs_for form, :responsibilities do
|
|
||||||
phx_label(inner_form, :description)
|
|
||||||
text_area(inner_form, :description)
|
|
||||||
_ = "Bob"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<form}
|
|
||||||
assert result =~ ~s{<input}
|
|
||||||
assert result =~ ~s{type="text"}
|
|
||||||
assert result =~ ~s{name="person[responsibilities][0][description]"}
|
|
||||||
assert result =~ ~s{name="person[responsibilities][1][description]"}
|
|
||||||
assert result =~ ~s{</form>}
|
|
||||||
refute result =~ ~s{Bob}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "helpers" do
|
|
||||||
test "generates a checkbox input" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
checkbox(form, :bob, class: "styles")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<input}
|
|
||||||
assert result =~ ~s{type="checkbox"}
|
|
||||||
assert result =~ ~s{class="styles"}
|
|
||||||
assert result =~ ~s{name="bob"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a color input" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
color_input(form, :bob, class: "styles")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<input}
|
|
||||||
assert result =~ ~s{type="color"}
|
|
||||||
assert result =~ ~s{class="styles"}
|
|
||||||
assert result =~ ~s{name="bob"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a date input" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
date_input(form, :bob, class: "date-styles")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<input}
|
|
||||||
assert result =~ ~s{type="date"}
|
|
||||||
assert result =~ ~s{class="date-styles"}
|
|
||||||
assert result =~ ~s{name="bob"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a date select input" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
date_select(form, :bob, class: "date-styles")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<select}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a datetime_local_input input" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
datetime_local_input(form, :bob, class: "date-styles")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<input}
|
|
||||||
assert result =~ ~s{type="datetime-local"}
|
|
||||||
assert result =~ ~s{class="date-styles"}
|
|
||||||
assert result =~ ~s{name="bob"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a datetime_select input" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
datetime_select(form, :bob, class: "datetime-select-styles")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<select}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a email input" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
email_input(form, :bob, class: "email-styles")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<input}
|
|
||||||
assert result =~ ~s{type="email"}
|
|
||||||
assert result =~ ~s{class="email-styles"}
|
|
||||||
assert result =~ ~s{name="bob"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a file input" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = [multipart: true]
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
file_input(form, :bob, class: "file-styles")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<input}
|
|
||||||
assert result =~ ~s{type="file"}
|
|
||||||
assert result =~ ~s{class="file-styles"}
|
|
||||||
assert result =~ ~s{name="bob"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a hidden input" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
hidden_input(form, :bob, class: "hidden-styles")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<input}
|
|
||||||
assert result =~ ~s{type="hidden"}
|
|
||||||
assert result =~ ~s{class="hidden-styles"}
|
|
||||||
assert result =~ ~s{name="bob"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a number input" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
number_input(form, :bob, class: "number-styles")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<input}
|
|
||||||
assert result =~ ~s{type="number"}
|
|
||||||
assert result =~ ~s{class="number-styles"}
|
|
||||||
assert result =~ ~s{name="bob"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a password input" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
password_input(form, :bob, class: "password-styles")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<input}
|
|
||||||
assert result =~ ~s{type="password"}
|
|
||||||
assert result =~ ~s{class="password-styles"}
|
|
||||||
assert result =~ ~s{name="bob"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a range input" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
range_input(form, :bob, class: "range-styles")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<input}
|
|
||||||
assert result =~ ~s{type="range"}
|
|
||||||
assert result =~ ~s{class="range-styles"}
|
|
||||||
assert result =~ ~s{name="bob"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a search input" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
search_input(form, :bob, class: "search-styles")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<input}
|
|
||||||
assert result =~ ~s{type="search"}
|
|
||||||
assert result =~ ~s{class="search-styles"}
|
|
||||||
assert result =~ ~s{name="bob"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a telephone input" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
telephone_input(form, :bob, class: "telephone-styles")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<input}
|
|
||||||
assert result =~ ~s{type="tel"}
|
|
||||||
assert result =~ ~s{class="telephone-styles"}
|
|
||||||
assert result =~ ~s{name="bob"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a radio button" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
radio_button(form, :bob, "1", class: "radio-styles")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<input}
|
|
||||||
assert result =~ ~s{type="radio"}
|
|
||||||
assert result =~ ~s{class="radio-styles"}
|
|
||||||
assert result =~ ~s{name="bob"}
|
|
||||||
assert result =~ ~s{value="1"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a text_area/2" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
text_area(form, :bob)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<textarea}
|
|
||||||
assert result =~ ~s{name="bob"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a text_area/3" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
text_area(form, :bob, class: "textarea-styles")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<textarea}
|
|
||||||
assert result =~ ~s{class="textarea-styles"}
|
|
||||||
assert result =~ ~s{name="bob"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a time input" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
time_input(form, :bob, class: "time-styles")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<input}
|
|
||||||
assert result =~ ~s{type="time"}
|
|
||||||
assert result =~ ~s{class="time-styles"}
|
|
||||||
assert result =~ ~s{name="bob"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a time_select input" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
time_select(form, :bob)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<select}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a url input" do
|
|
||||||
conn = %Plug.Conn{}
|
|
||||||
action = "/"
|
|
||||||
opts = []
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
form_for conn, action, opts do
|
|
||||||
url_input(form, :bob, class: "url-styles")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<input}
|
|
||||||
assert result =~ ~s{type="url"}
|
|
||||||
assert result =~ ~s{class="url-styles"}
|
|
||||||
assert result =~ ~s{name="bob"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a reset input" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
reset("Reset", class: "reset-styles")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<input}
|
|
||||||
assert result =~ ~s{type="reset}
|
|
||||||
assert result =~ ~s{class="reset-styles"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a submit/1 input" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
submit("Submit")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert String.starts_with?(result, ~s{<button})
|
|
||||||
assert result =~ ~s{type="submit}
|
|
||||||
assert result =~ ~s{Submit}
|
|
||||||
assert String.ends_with?(result, ~s{</button>})
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a submit/1 input that takes a block" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
submit do
|
|
||||||
div do
|
|
||||||
text "Submit"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert String.starts_with?(result, ~s{<button})
|
|
||||||
assert result =~ ~s{type="submit}
|
|
||||||
assert result =~ ~s{<div>}
|
|
||||||
assert result =~ ~s{Submit}
|
|
||||||
assert result =~ ~s{</div>}
|
|
||||||
assert String.ends_with?(result, ~s{</button>})
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a submit/2 input that takes text and opts" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
submit("Submit", class: "btn")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert String.starts_with?(result, ~s{<button})
|
|
||||||
assert result =~ ~s{type="submit}
|
|
||||||
assert result =~ ~s{class="btn"}
|
|
||||||
assert result =~ ~s{Submit}
|
|
||||||
assert String.ends_with?(result, ~s{</button>})
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a submit/2 input that takes opts and a block" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
submit class: "btn" do
|
|
||||||
div do
|
|
||||||
text "Submit"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert String.starts_with?(result, ~s{<button})
|
|
||||||
assert result =~ ~s{type="submit}
|
|
||||||
assert result =~ ~s{class="btn"}
|
|
||||||
assert result =~ ~s{<div>}
|
|
||||||
assert result =~ ~s{Submit}
|
|
||||||
assert result =~ ~s{</div>}
|
|
||||||
assert String.ends_with?(result, ~s{</button>})
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a phx_label/2 tag" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
phx_label(:user, :name)
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<label}
|
|
||||||
assert result =~ ~s{for="user_name"}
|
|
||||||
assert result =~ ~s{Name}
|
|
||||||
assert result =~ ~s{</label>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a phx_label/3 with attrs" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
phx_label(:user, :name, class: "label-style")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<label}
|
|
||||||
assert result =~ ~s{for="user_name"}
|
|
||||||
assert result =~ ~s{class="label-style"}
|
|
||||||
assert result =~ ~s{Name}
|
|
||||||
assert result =~ ~s{</label>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a phx_label/3 with text" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
phx_label(:user, :name, "Name")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<label}
|
|
||||||
assert result =~ ~s{for="user_name"}
|
|
||||||
assert result =~ ~s{Name}
|
|
||||||
assert result =~ ~s{</label>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a phx_label/3 with block" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
phx_label :user, :name do
|
|
||||||
div do
|
|
||||||
text "Name"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert String.starts_with?(result, ~s{<label})
|
|
||||||
assert result =~ ~s{for="user_name"}
|
|
||||||
assert result =~ ~s{<div>}
|
|
||||||
assert result =~ ~s{Name}
|
|
||||||
assert result =~ ~s{</div>}
|
|
||||||
assert String.ends_with?(result, ~s{</label>})
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a phx_label/4 with text and opts" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
phx_label(:user, :name, "Name", class: "label-style")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<label}
|
|
||||||
assert result =~ ~s{for="user_name"}
|
|
||||||
assert result =~ ~s{class="label-style"}
|
|
||||||
assert result =~ ~s{Name}
|
|
||||||
assert result =~ ~s{</label>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a phx_label/4 with block" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
phx_label :user, :name, class: "label-style" do
|
|
||||||
div do
|
|
||||||
text "Name"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert String.starts_with?(result, ~s{<label})
|
|
||||||
assert result =~ ~s{for="user_name"}
|
|
||||||
assert result =~ ~s{class="label-style"}
|
|
||||||
assert result =~ ~s{<div>}
|
|
||||||
assert result =~ ~s{Name}
|
|
||||||
assert result =~ ~s{</div>}
|
|
||||||
assert String.ends_with?(result, ~s{</label>})
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a multiple_select tag" do
|
|
||||||
options = [
|
|
||||||
Alice: 1,
|
|
||||||
Bob: 2,
|
|
||||||
Carol: 3
|
|
||||||
]
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
multiple_select(:user, :name, options, class: "label-style")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<select}
|
|
||||||
assert result =~ ~s{name="user[name][]"}
|
|
||||||
assert result =~ ~s{class="label-style"}
|
|
||||||
assert result =~ ~s{multiple=""}
|
|
||||||
assert result =~ ~s{<option}
|
|
||||||
assert result =~ ~s{value="1"}
|
|
||||||
assert result =~ ~s{Alice}
|
|
||||||
assert result =~ ~s{value="2"}
|
|
||||||
assert result =~ ~s{Bob}
|
|
||||||
assert result =~ ~s{value="3"}
|
|
||||||
assert result =~ ~s{Carol}
|
|
||||||
assert result =~ ~s{</select>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generates a select tag" do
|
|
||||||
options = [
|
|
||||||
Alice: 1,
|
|
||||||
Bob: 2,
|
|
||||||
Carol: 3
|
|
||||||
]
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
select :user, :name, options, class: "label-style"
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result =~ ~s{<select}
|
|
||||||
assert result =~ ~s{name="user[name]"}
|
|
||||||
assert result =~ ~s{class="label-style"}
|
|
||||||
assert result =~ ~s{<option}
|
|
||||||
assert result =~ ~s{value="1"}
|
|
||||||
assert result =~ ~s{Alice}
|
|
||||||
assert result =~ ~s{value="2"}
|
|
||||||
assert result =~ ~s{Bob}
|
|
||||||
assert result =~ ~s{value="3"}
|
|
||||||
assert result =~ ~s{Carol}
|
|
||||||
assert result =~ ~s{</select>}
|
|
||||||
|
|
||||||
refute result =~ ~s{multiple=""}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,335 +0,0 @@
|
||||||
defmodule Temple.HtmlTest do
|
|
||||||
use ExUnit.Case, async: true
|
|
||||||
use Temple
|
|
||||||
use Temple.Support.Utils
|
|
||||||
|
|
||||||
test "renders a html with a block" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
html(do: div())
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<!DOCTYPE html><html><div></div></html>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a html with attrs and a block" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
html(class: "hello") do
|
|
||||||
div()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<!DOCTYPE html><html class="hello"><div></div></html>}
|
|
||||||
end
|
|
||||||
|
|
||||||
for tag <- Temple.Html.nonvoid_elements() do
|
|
||||||
test "renders a #{tag}" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
unquote(tag)()
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<#{unquote(tag)}></#{unquote(tag)}>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a #{tag} with attrs" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
unquote(tag)(class: "hello")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<#{unquote(tag)} class="hello"></#{unquote(tag)}>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a #{tag} with content" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
unquote(tag)("Hi")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == "<#{unquote(tag)}>Hi</#{unquote(tag)}>"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a #{tag} with escaped content" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
unquote(tag)("<div>1</div>")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == "<#{unquote(tag)}><div>1</div></#{unquote(tag)}>"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a #{tag} with attrs and content" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
unquote(tag)("Hi", class: "hello")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<#{unquote(tag)} class="hello">Hi</#{unquote(tag)}>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a #{tag} with a block" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
unquote(tag)(do: unquote(tag)())
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<#{unquote(tag)}><#{unquote(tag)}></#{unquote(tag)}></#{unquote(tag)}>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a #{tag} with attrs and a block" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
unquote(tag)(class: "hello") do
|
|
||||||
unquote(tag)()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result ==
|
|
||||||
~s{<#{unquote(tag)} class="hello"><#{unquote(tag)}></#{unquote(tag)}></#{
|
|
||||||
unquote(tag)
|
|
||||||
}>}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
for tag <- Temple.Html.void_elements() do
|
|
||||||
test "renders a #{tag}" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
unquote(tag)()
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<#{unquote(tag)}>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a #{tag} with attrs" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
unquote(tag)(class: "hello")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<#{unquote(tag)} class="hello">}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "non-void elements" do
|
|
||||||
test "renders two divs" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
div()
|
|
||||||
div()
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == "<div></div><div></div>"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders two els in the right order" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
div()
|
|
||||||
span()
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == "<div></div><span></span>"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders an el that taks attrs and a block" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
div class: "bob" do
|
|
||||||
span()
|
|
||||||
span()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<div class="bob"><span></span><span></span></div>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders one els nested inside an el" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
div do
|
|
||||||
span()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == "<div><span></span></div>"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders two els nested inside an el" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
div do
|
|
||||||
span()
|
|
||||||
span()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == "<div><span></span><span></span></div>"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders two divs that are rendered by a loop" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
for _ <- 1..2 do
|
|
||||||
div()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == "<div></div><div></div>"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders two spans" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
span()
|
|
||||||
span()
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == "<span></span><span></span>"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a div within a div" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
div do
|
|
||||||
div()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == "<div><div></div></div>"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders an attribute on a div" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
div class: "hello" do
|
|
||||||
div class: "hi"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<div class="hello"><div class="hi"></div></div>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders an attribute passed in as a map on a div" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
div %{class: "hello"} do
|
|
||||||
div %{"class" => "hi"}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<div class="hello"><div class="hi"></div></div>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders an attribute on a div passed as a variable" do
|
|
||||||
attrs1 = [class: "hello"]
|
|
||||||
attrs2 = [class: "hi"]
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
div attrs1 do
|
|
||||||
div attrs2
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<div class="hello"><div class="hi"></div></div>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders multiple attributes on a div without block" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
div class: "hello", id: "12"
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<div class="hello" id="12"></div>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "can accept content as the first argument" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
div "CONTENT"
|
|
||||||
div "MORE", class: "hi"
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<div>CONTENT</div><div class="hi">MORE</div>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "can accept content as first argument passed as a variable" do
|
|
||||||
content = "CONTENT"
|
|
||||||
more = "MORE"
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
div content
|
|
||||||
div more, class: "hi"
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<div>CONTENT</div><div class="hi">MORE</div>}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "void elements" do
|
|
||||||
test "renders an input" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
input()
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<input>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders an input with an attribute" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
input type: "number"
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<input type="number">}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "can use string interpolation in an attribute" do
|
|
||||||
interop = "hi"
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
div class: "#{interop} world"
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<div class="hi world"></div>}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "escaping" do
|
|
||||||
test "text is excaped" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
text "<div>Text</div>"
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<div>Text</div>}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "data attributes" do
|
|
||||||
test "can have one data attributes" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
div data_controller: "stimulus-controller"
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<div data-controller="stimulus-controller"></div>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "can have multiple data attributes" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
div data_controller: "stimulus-controller", data_target: "stimulus-target"
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result ==
|
|
||||||
~s{<div data-controller="stimulus-controller" data-target="stimulus-target"></div>}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,171 +0,0 @@
|
||||||
defmodule Temple.LinkTest do
|
|
||||||
use ExUnit.Case, async: true
|
|
||||||
use Temple
|
|
||||||
use Temple.Support.Utils
|
|
||||||
|
|
||||||
describe "phx_link" do
|
|
||||||
test "emits a link" do
|
|
||||||
{:safe, actual} =
|
|
||||||
temple do
|
|
||||||
phx_link("hi", to: "/hello")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert actual =~ ~s{<a}
|
|
||||||
assert actual =~ ~s{href="/hello"}
|
|
||||||
assert actual =~ ~s{hi}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "emits a link when passed block that has text" do
|
|
||||||
{:safe, actual} =
|
|
||||||
temple do
|
|
||||||
phx_link to: "/hello" do
|
|
||||||
text "hi"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert String.starts_with?(actual, ~s{<a})
|
|
||||||
assert actual =~ ~s{href="/hello"}
|
|
||||||
assert actual =~ ~s{hi}
|
|
||||||
assert String.ends_with?(actual, ~s{</a>})
|
|
||||||
end
|
|
||||||
|
|
||||||
test "emits a link when passed block that has more markup" do
|
|
||||||
{:safe, actual} =
|
|
||||||
temple do
|
|
||||||
phx_link to: "/hello" do
|
|
||||||
div do
|
|
||||||
div "hi"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert String.starts_with?(actual, ~s{<a})
|
|
||||||
assert actual =~ ~s{href="/hello"}
|
|
||||||
assert actual =~ ~s{<div><div>}
|
|
||||||
assert actual =~ ~s{hi}
|
|
||||||
assert actual =~ ~s{</div></div>}
|
|
||||||
assert String.ends_with?(actual, ~s{</a>})
|
|
||||||
end
|
|
||||||
|
|
||||||
test "emits a link with additional html attributes" do
|
|
||||||
{:safe, actual} =
|
|
||||||
temple do
|
|
||||||
phx_link("hi",
|
|
||||||
to: "/hello",
|
|
||||||
class: "phoenix",
|
|
||||||
id: "legendary",
|
|
||||||
data: [confirm: "Really?"],
|
|
||||||
method: :delete
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
assert actual =~ ~s{<a}
|
|
||||||
assert actual =~ ~s{href="/hello"}
|
|
||||||
assert actual =~ ~s{class="phoenix"}
|
|
||||||
assert actual =~ ~s{id="legendary"}
|
|
||||||
assert actual =~ ~s{data-confirm="Really?"}
|
|
||||||
assert actual =~ ~s{hi}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "emits a link with a non GET method" do
|
|
||||||
{:safe, actual} =
|
|
||||||
temple do
|
|
||||||
phx_link("hi",
|
|
||||||
to: "/hello",
|
|
||||||
method: :delete
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
assert actual =~ ~s{<a}
|
|
||||||
assert actual =~ ~s{data-csrf="}
|
|
||||||
assert actual =~ ~s{data-method="delete"}
|
|
||||||
assert actual =~ ~s{data-to="/hello"}
|
|
||||||
assert actual =~ ~s{hi}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "phx_button" do
|
|
||||||
test "emits a button" do
|
|
||||||
{:safe, actual} =
|
|
||||||
temple do
|
|
||||||
phx_button("hi", to: "/hello")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert actual =~ ~s{<button}
|
|
||||||
assert actual =~ ~s{data-to="/hello"}
|
|
||||||
assert actual =~ ~s{data-method="post"}
|
|
||||||
assert actual =~ ~s{hi}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "emits a button when passed block that has text" do
|
|
||||||
{:safe, actual} =
|
|
||||||
temple do
|
|
||||||
phx_button to: "/hello" do
|
|
||||||
text "hi"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert String.starts_with?(actual, ~s{<button})
|
|
||||||
assert actual =~ ~s{hi}
|
|
||||||
assert actual =~ ~s{data-to="/hello"}
|
|
||||||
assert actual =~ ~s{data-method="post"}
|
|
||||||
assert String.ends_with?(actual, ~s{</button>})
|
|
||||||
end
|
|
||||||
|
|
||||||
test "emits a button when passed block that has more markup" do
|
|
||||||
{:safe, actual} =
|
|
||||||
temple do
|
|
||||||
phx_button to: "/hello" do
|
|
||||||
div do
|
|
||||||
div "hi"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert String.starts_with?(actual, ~s{<button})
|
|
||||||
assert actual =~ ~s{data-to="/hello"}
|
|
||||||
assert actual =~ ~s{data-method="post"}
|
|
||||||
assert actual =~ ~s{<div><div>}
|
|
||||||
assert actual =~ ~s{hi}
|
|
||||||
assert actual =~ ~s{</div></div>}
|
|
||||||
assert String.ends_with?(actual, ~s{</button>})
|
|
||||||
end
|
|
||||||
|
|
||||||
test "emits a button with additional html attributes" do
|
|
||||||
{:safe, actual} =
|
|
||||||
temple do
|
|
||||||
phx_button("hi",
|
|
||||||
to: "/hello",
|
|
||||||
class: "phoenix",
|
|
||||||
id: "legendary",
|
|
||||||
data: [confirm: "Really?"],
|
|
||||||
method: :delete
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
assert String.starts_with?(actual, ~s{<button})
|
|
||||||
assert actual =~ ~s{class="phoenix"}
|
|
||||||
assert actual =~ ~s{id="legendary"}
|
|
||||||
assert actual =~ ~s{data-confirm="Really?"}
|
|
||||||
assert actual =~ ~s{hi}
|
|
||||||
assert String.ends_with?(actual, ~s{</button>})
|
|
||||||
end
|
|
||||||
|
|
||||||
test "emits a button with a non GET method" do
|
|
||||||
{:safe, actual} =
|
|
||||||
temple do
|
|
||||||
phx_button("hi",
|
|
||||||
to: "/hello",
|
|
||||||
method: :delete
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
assert String.starts_with?(actual, ~s{<button})
|
|
||||||
assert actual =~ ~s{data-csrf="}
|
|
||||||
assert actual =~ ~s{data-method="delete"}
|
|
||||||
assert actual =~ ~s{data-to="/hello"}
|
|
||||||
assert actual =~ ~s{hi}
|
|
||||||
assert String.ends_with?(actual, ~s{</button>})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,150 +0,0 @@
|
||||||
defmodule Temple.SvgTest do
|
|
||||||
use ExUnit.Case, async: true
|
|
||||||
import Temple
|
|
||||||
import Temple.Utils, only: [to_valid_tag: 1]
|
|
||||||
use Temple.Support.Utils
|
|
||||||
|
|
||||||
for tag <- Temple.Svg.elements() -- [:text_] do
|
|
||||||
test "renders a #{tag}" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
unquote(tag)()
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<#{to_valid_tag(unquote(tag))}></#{to_valid_tag(unquote(tag))}>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a #{tag} with attrs" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
unquote(tag)(class: "hello")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result ==
|
|
||||||
~s{<#{to_valid_tag(unquote(tag))} class="hello"></#{to_valid_tag(unquote(tag))}>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a #{tag} with content" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
unquote(tag)("Hi")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == "<#{to_valid_tag(unquote(tag))}>Hi</#{to_valid_tag(unquote(tag))}>"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a #{tag} with escaped content" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
unquote(tag)("<div>1</div>")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result ==
|
|
||||||
"<#{to_valid_tag(unquote(tag))}><div>1</div></#{
|
|
||||||
to_valid_tag(unquote(tag))
|
|
||||||
}>"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a #{tag} with attrs and content" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
unquote(tag)("Hi", class: "hello")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result ==
|
|
||||||
~s{<#{to_valid_tag(unquote(tag))} class="hello">Hi</#{to_valid_tag(unquote(tag))}>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a #{tag} with a block" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
unquote(tag)(do: unquote(tag)())
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result ==
|
|
||||||
~s{<#{to_valid_tag(unquote(tag))}><#{to_valid_tag(unquote(tag))}></#{
|
|
||||||
to_valid_tag(unquote(tag))
|
|
||||||
}></#{to_valid_tag(unquote(tag))}>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a #{tag} with attrs and a block" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
unquote(tag)(class: "hello") do
|
|
||||||
unquote(tag)()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result ==
|
|
||||||
~s{<#{to_valid_tag(unquote(tag))} class="hello"><#{to_valid_tag(unquote(tag))}></#{
|
|
||||||
to_valid_tag(unquote(tag))
|
|
||||||
}></#{to_valid_tag(unquote(tag))}>}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a text" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
text_()
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<text></text>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a text with attrs" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
text_(class: "hello")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<text class="hello"></text>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a text with content" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
text_("Hi")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == "<text>Hi</text>"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a text with escaped content" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
text_("<div>1</div>")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == "<text><div>1</div></text>"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a text with attrs and content" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
text_("Hi", class: "hello")
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<text class="hello">Hi</text>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a text with a block" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
text_(do: text_())
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<text><text></text></text>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "renders a text with attrs and a block" do
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
text_(class: "hello") do
|
|
||||||
text_()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result ==
|
|
||||||
~s{<text class="hello"><text></text></text>}
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,23 +0,0 @@
|
||||||
defmodule Temple.UtilsTest do
|
|
||||||
use ExUnit.Case, async: true
|
|
||||||
|
|
||||||
describe "from_safe/1" do
|
|
||||||
test "returns a the text from a safe partial" do
|
|
||||||
expected = "I am safe!"
|
|
||||||
partial = {:safe, expected}
|
|
||||||
|
|
||||||
result = Temple.Utils.from_safe(partial)
|
|
||||||
|
|
||||||
assert result == expected
|
|
||||||
end
|
|
||||||
|
|
||||||
test "escapes an unsafe partial and returns the text" do
|
|
||||||
expected = "I am <safe>!"
|
|
||||||
partial = "I am <safe>!"
|
|
||||||
|
|
||||||
result = Temple.Utils.from_safe(partial)
|
|
||||||
|
|
||||||
assert result == expected
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -3,216 +3,266 @@ defmodule TempleTest do
|
||||||
use Temple
|
use Temple
|
||||||
use Temple.Support.Utils
|
use Temple.Support.Utils
|
||||||
|
|
||||||
describe "custom component" do
|
test "renders an attribute on a div passed as a variable" do
|
||||||
test "defcomponent works when requiring the module" do
|
result =
|
||||||
require Temple.Support.Component, as: C
|
temple do
|
||||||
|
div class: "hello" do
|
||||||
|
div class: "hi"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
{:safe, result} =
|
assert result == ~s{<div class="hello"><div class="hi"></div></div>}
|
||||||
temple do
|
end
|
||||||
C.flex()
|
|
||||||
|
|
||||||
C.flex([])
|
test "renders void element" do
|
||||||
C.flex([], [])
|
result =
|
||||||
|
temple do
|
||||||
|
input name: "password"
|
||||||
|
end
|
||||||
|
|
||||||
C.flex do
|
assert result == ~s{<input name="password">}
|
||||||
text "hi"
|
end
|
||||||
|
|
||||||
|
test "renders a text node from the text keyword with siblings" do
|
||||||
|
result =
|
||||||
|
temple do
|
||||||
|
div class: "hello" do
|
||||||
|
"hi"
|
||||||
|
"foo"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert result == ~s{<div class="hello">hifoo</div>}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders a variable text node as eex" do
|
||||||
|
result =
|
||||||
|
temple do
|
||||||
|
div class: "hello" do
|
||||||
|
foo
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert result == ~s{<div class="hello"><%= foo %></div>}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders an assign text node as eex" do
|
||||||
|
result =
|
||||||
|
temple do
|
||||||
|
div class: "hello" do
|
||||||
|
@foo
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert result == ~s{<div class="hello"><%= @foo %></div>}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders a match expression" do
|
||||||
|
result =
|
||||||
|
temple do
|
||||||
|
x = 420
|
||||||
|
|
||||||
|
div do
|
||||||
|
"blaze it"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert result == ~s{<% x = 420 %><div>blaze it</div>}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders a non-match expression" do
|
||||||
|
result =
|
||||||
|
temple do
|
||||||
|
IO.inspect(:foo)
|
||||||
|
|
||||||
|
div do
|
||||||
|
"bar"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert result == ~s{<%= IO.inspect(:foo) %><div>bar</div>}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders an expression in attr as eex" do
|
||||||
|
result =
|
||||||
|
temple do
|
||||||
|
div class: foo <> " bar"
|
||||||
|
end
|
||||||
|
|
||||||
|
assert result == ~s{<div class="<%= foo <> " bar" %>"></div>}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders an attribute on a div passed as a variable as eex" do
|
||||||
|
result =
|
||||||
|
temple do
|
||||||
|
div class: Enum.map([:one, :two], fn x -> x end) do
|
||||||
|
div class: "hi"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert result ==
|
||||||
|
~s{<div class="<%= Enum.map([:one, :two], fn x -> x end) %>"><div class="hi"></div></div>}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders a for comprehension as eex" do
|
||||||
|
result =
|
||||||
|
temple do
|
||||||
|
for x <- 1..5 do
|
||||||
|
div class: "hi"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert result == ~s{<%= for(x <- 1..5) do %><div class="hi"></div><% end %>}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders an if expression as eex" do
|
||||||
|
result =
|
||||||
|
temple do
|
||||||
|
if true == false do
|
||||||
|
div class: "hi"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert result == ~s{<%= if(true == false) do %><div class="hi"></div><% end %>}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders an if/else expression as eex" do
|
||||||
|
result =
|
||||||
|
temple do
|
||||||
|
if true == false do
|
||||||
|
div class: "hi"
|
||||||
|
else
|
||||||
|
div class: "haha"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert result ==
|
||||||
|
~s{<%= if(true == false) do %><div class="hi"></div><% else %><div class="haha"></div><% end %>}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders an unless expression as eex" do
|
||||||
|
result =
|
||||||
|
temple do
|
||||||
|
unless true == false do
|
||||||
|
div class: "hi"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert result == ~s{<%= unless(true == false) do %><div class="hi"></div><% end %>}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders multiline anonymous function with 1 arg before the function" do
|
||||||
|
result =
|
||||||
|
temple do
|
||||||
|
form_for Routes.user_path(@conn, :create), fn f ->
|
||||||
|
"Name: "
|
||||||
|
text_input f, :name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert result ==
|
||||||
|
~s{<%= form_for Routes.user_path(@conn, :create), fn f -> %>Name: <%= text_input(f, :name) %><% end %>}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders multiline anonymous functions with 2 args before the function" do
|
||||||
|
result =
|
||||||
|
temple do
|
||||||
|
form_for @changeset, Routes.user_path(@conn, :create), fn f ->
|
||||||
|
"Name: "
|
||||||
|
text_input f, :name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
assert result ==
|
||||||
|
~s{<%= form_for @changeset, Routes.user_path(@conn, :create), fn f -> %>Name: <%= text_input(f, :name) %><% end %>}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders multiline anonymous functions with complex nested children" do
|
||||||
|
result =
|
||||||
|
temple do
|
||||||
|
form_for @changeset, Routes.user_path(@conn, :create), fn f ->
|
||||||
|
div do
|
||||||
|
"Name: "
|
||||||
|
text_input f, :name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
assert result ==
|
assert result ==
|
||||||
~s{<div class="flex"></div><div class="flex"></div><div class="flex"></div><div class="flex"></div>}
|
~s{<%= form_for @changeset, Routes.user_path(@conn, :create), fn f -> %><div>Name: <%= text_input(f, :name) %></div><% end %>}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "defines a basic component" do
|
test "renders multiline anonymous function with 3 arg before the function" do
|
||||||
import Temple.Support.Component
|
result =
|
||||||
|
temple do
|
||||||
{:safe, result} =
|
form_for @changeset, Routes.user_path(@conn, :create), [foo: :bar], fn f ->
|
||||||
temple do
|
"Name: "
|
||||||
flex()
|
text_input f, :name
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
assert result == ~s{<div class="flex"></div>}
|
assert result ==
|
||||||
end
|
~s{<%= form_for @changeset, Routes.user_path(@conn, :create), [foo: :bar], fn f -> %>Name: <%= text_input(f, :name) %><% end %>}
|
||||||
|
end
|
||||||
|
|
||||||
test "defines a component that takes 1 child" do
|
test "renders multiline anonymous function with 1 arg before the function and 1 arg after" do
|
||||||
import Temple.Support.Component
|
result =
|
||||||
|
temple do
|
||||||
|
form_for @changeset,
|
||||||
|
fn f ->
|
||||||
|
"Name: "
|
||||||
|
text_input f, :name
|
||||||
|
end,
|
||||||
|
foo: :bar
|
||||||
|
end
|
||||||
|
|
||||||
{:safe, result} =
|
assert result ==
|
||||||
temple do
|
~s{<%= form_for @changeset, fn f -> %>Name: <%= text_input(f, :name) %><% end, [foo: :bar] %>}
|
||||||
takes_children do
|
end
|
||||||
div id: "dynamic-child"
|
|
||||||
|
test "tags prefixed with Temple. should be interpreted as temple tags" do
|
||||||
|
result =
|
||||||
|
temple do
|
||||||
|
div do
|
||||||
|
Temple.span do
|
||||||
|
"bob"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
assert result ==
|
assert result == ~s{<div><span>bob</span></div>}
|
||||||
~s{<div><div id="static-child-1"></div><div id="dynamic-child"></div><div id="static-child-2"></div></div>}
|
end
|
||||||
end
|
|
||||||
|
|
||||||
test "defines a component that takes multiple children" do
|
test "can pass do as an arg instead of a block" do
|
||||||
import Temple.Support.Component
|
result =
|
||||||
|
temple do
|
||||||
{:safe, result} =
|
div class: "font-bold" do
|
||||||
temple do
|
"Hello, world"
|
||||||
takes_children do
|
|
||||||
div id: "dynamic-child-1"
|
|
||||||
div id: "dynamic-child-2"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
assert result ==
|
div class: "font-bold", do: "Hello, world"
|
||||||
~s{<div><div id="static-child-1"></div><div id="dynamic-child-1"></div><div id="dynamic-child-2"></div><div id="static-child-2"></div></div>}
|
div do: "Hello, world"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "can access a prop" do
|
assert result ==
|
||||||
import Temple.Support.Component
|
~s{<div class="font-bold">Hello, world</div><div class="font-bold">Hello, world</div><div>Hello, world</div>}
|
||||||
|
end
|
||||||
|
|
||||||
{:safe, result} =
|
test "passing 'compact: true' will not insert new lines" do
|
||||||
temple do
|
import Temple.Support.Utils, only: []
|
||||||
takes_children name: "mitch" do
|
import Kernel
|
||||||
text @name
|
|
||||||
end
|
result =
|
||||||
|
temple do
|
||||||
|
p compact: true do
|
||||||
|
"Bob"
|
||||||
end
|
end
|
||||||
|
|
||||||
assert result ==
|
p compact: true do
|
||||||
~s{<div><div id="static-child-1"></div>mitch<div id="static-child-2"></div></div>}
|
foo
|
||||||
end
|
|
||||||
|
|
||||||
test "can access assigns list" do
|
|
||||||
import Temple.Support.Component
|
|
||||||
|
|
||||||
assigns = [foo: "bar", hello: "world"]
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
lists_assigns(assigns)
|
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
assert result == inspect(assigns)
|
assert result == ~s{<p>Bob</p>\n<p><%= foo %></p>}
|
||||||
end
|
|
||||||
|
|
||||||
test "can access assigns map" do
|
|
||||||
import Temple.Support.Component
|
|
||||||
|
|
||||||
assigns = %{foo: "bar", hello: "world"}
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
lists_assigns(assigns)
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == inspect(assigns)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "can have arbitrary code inside the definition" do
|
|
||||||
import Temple.Support.Component
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
arbitrary_code()
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<div>55</div>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "can use conditionals to render different markup" do
|
|
||||||
import Temple.Support.Component
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
uses_conditionals(condition: true)
|
|
||||||
uses_conditionals(condition: false)
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s{<div></div><span></span>}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "can pass arbitrary data as assigns" do
|
|
||||||
import Temple.Support.Component
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
arbitrary_data(
|
|
||||||
lists: [:atom, %{key: "value"}, {:status, :tuple}, "string", 1, [1, 2, 3]]
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result ==
|
|
||||||
~s|<div>:atom</div><div>%{key: "value"}</div><div>{:status, :tuple}</div><div>"string"</div><div>1</div><div>[1, 2, 3]</div>|
|
|
||||||
end
|
|
||||||
|
|
||||||
test "can pass a variable as a prop" do
|
|
||||||
import Temple.Support.Component
|
|
||||||
|
|
||||||
bob = "hi"
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
variable_as_prop(bob: bob)
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s|<div id="hi"></div>|
|
|
||||||
end
|
|
||||||
|
|
||||||
test "can pass a variable as a prop to a component with a block" do
|
|
||||||
import Temple.Support.Component
|
|
||||||
|
|
||||||
bob = "hi"
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
variable_as_prop_with_block bob: bob do
|
|
||||||
div()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s|<div id="hi"><div></div></div>|
|
|
||||||
end
|
|
||||||
|
|
||||||
test "can pass all of the assigns as a variable" do
|
|
||||||
import Temple.Support.Component
|
|
||||||
|
|
||||||
assigns = [bob: "hi"]
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
variable_as_prop(assigns)
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s|<div id="hi"></div>|
|
|
||||||
end
|
|
||||||
|
|
||||||
test "can pass all of the assigns as a variable with a block" do
|
|
||||||
import Temple.Support.Component
|
|
||||||
|
|
||||||
assigns = [bob: "hi"]
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
variable_as_prop_with_block assigns do
|
|
||||||
div()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s|<div id="hi"><div></div></div>|
|
|
||||||
end
|
|
||||||
|
|
||||||
test "can pass a map as assigns with a block" do
|
|
||||||
import Temple.Support.Component
|
|
||||||
|
|
||||||
assigns = %{bob: "hi"}
|
|
||||||
|
|
||||||
{:safe, result} =
|
|
||||||
temple do
|
|
||||||
variable_as_prop_with_block assigns do
|
|
||||||
div()
|
|
||||||
end
|
|
||||||
|
|
||||||
variable_as_prop_with_block %{bob: "hi"} do
|
|
||||||
div()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
assert result == ~s|<div id="hi"><div></div></div><div id="hi"><div></div></div>|
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Reference in New Issue