Adicione blogs, documentação e outras páginas estáticas aos aplicativos Phoenix. Esta biblioteca se integra perfeitamente ao seu roteador e vem com suporte integrado para renderização de markdown com frontmatter, realce de sintaxe, cache em tempo de compilação e muito mais.
def deps do
[
{ :phoenix_pages , "~> 0.1" }
]
end
A maneira recomendada de instalar em seu aplicativo Phoenix é adicionar isso à função do router
em lib/myapp_web.ex
, substituindo myapp
pelo nome do seu aplicativo:
def router do
quote do
use Phoenix.Router , helpers: false
use PhoenixPages , otp_app: :myapp
# ...
end
end
Agora você pode adicionar uma nova rota usando a macro pages/4
:
scope "/" , MyAppWeb do
pipe_through :browser
get "/" , PageController , :home
pages "/:page" , PageController , :show , from: "priv/pages/**/*.md"
end
Isso lerá todos os arquivos markdown de priv/pages
e criará uma nova rota GET para cada um. O segmento :page
será substituído pelo caminho e nome do arquivo (sem a extensão) relativo ao diretório base (veja Definindo Caminhos).
Você também precisará adicionar o manipulador :show
a lib/myapp_web/controllers/page_controller.ex
:
defmodule MyAppWeb.PageController do
use MyAppWeb , :controller
# ...
def show ( conn , _params ) do
render ( conn , "show.html" )
end
end
Por último, adicione um modelo em lib/myapp_web/controllers/page_html/show.html.heex
. O markdown renderizado da página estará disponível na atribuição inner_content
:
< main >
<%= @inner_content % >
</ main >
É isso! Agora tente criar um arquivo em priv/pages/hello.md
e visitar /hello
.
Para evitar que mix format
adicione parênteses à macro pages
semelhante às outras macros do roteador Phoenix, adicione :phoenix_pages
a .formatter.exs
:
[
import_deps: [ :ecto , :ecto_sql , :phoenix , :phoenix_pages ]
]
O Frontmatter permite que variáveis específicas da página sejam incluídas na parte superior de um arquivo markdown usando o formato YAML. Se você estiver definindo variáveis de frontmatter (que são opcionais), elas devem ser a primeira coisa no arquivo e devem ser definidas entre linhas tracejadas triplas:
---
title : Hello World
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
nisi ut aliquip ex ea commodo consequat.
Para especificar quais valores de frontmatter são esperados em cada página, defina a opção attrs
:
pages "/:page" , PageController , :show ,
from: "priv/pages/**/*.md" ,
attrs: [ :title , author: nil ]
Os valores Atom serão considerados obrigatórios e um erro de compilação será gerado se estiverem faltando em alguma das páginas. Os valores-chave devem vir por último na lista e serão considerados opcionais pela definição de um valor padrão. Quaisquer valores de frontmatter não definidos na lista de atributos serão descartados silenciosamente.
Valores de atributos válidos estarão disponíveis nas atribuições:
< main >
< h1 > <%= @title % > </ h1 >
< h2 :if = {@author} > <%= @author % > </ h2 >
<%= @inner_content % >
</ main >
Phoenix Pages usa o projeto Makeup para realce de sintaxe. Para habilitar, adicione um lexer para seu(s) idioma(s) específico(s) às dependências do projeto. Phoenix Pages pegará a nova dependência e começará a destacar seus blocos de código sem qualquer configuração adicional. Nenhum lexer é incluído por padrão.
`{:makeup_c, "~> 0.0"}`
`{:makeup_diff, "~> 0.0"}`
`{:makeup_elixir, "~> 0.0"}`
`{:makeup_erlang, "~> 0.0"}`
`{:makeup_graphql, "~> 0.0"}`
`{:makeup_eex, "~> 0.0"}`
`{:makeup_html, "~> 0.0"}`
`{:makeup_js, "~> 0.0"}`
`{:makeup_json, "~> 0.0"}`
`{:makeup_rust, "~> 0.0"}`
`{:makeup_sql, "~> 0.0"}`
Se o idioma de sua escolha não for compatível, considere escrever um novo lexer de maquiagem para contribuir com a comunidade. Caso contrário, você pode usar um marcador de sintaxe baseado em JS, como destaque.js, definindo code_class_prefix: "language-"
e syntax_highlighting: false
em render_options
.
A seguir, importe um tema listado abaixo para o seu pacote CSS. As especificidades de fazer isso dependem muito da sua configuração CSS, mas alguns exemplos estão incluídos abaixo. Na maioria dos casos, você precisará importar phoenix_pages/css/monokai.css
(ou qualquer tema que você escolher) para seu pacote e garantir que deps
esteja incluído como um diretório de fornecedores.
Usando o instalador ESBuild, adicione a opção env
a config/config.exs
:
config :esbuild ,
version: "0.17.18" ,
default: [
cd: Path . expand ( "../assets" , __DIR__ ) ,
env: % { "NODE_PATH" => Path . expand ( "../deps" , __DIR__ ) } ,
args: ~w ( --bundle --outdir=../priv/static/assets js/app.js )
]
Então em app.js
:
import "phoenix_pages/css/monokai.css" ;
Usando o instalador Sass, adicione o sinalizador --load-path
a config/config.exs
:
config :dart_sass ,
version: "1.62.0" ,
default: [
cd: Path . expand ( "../assets" , __DIR__ ) ,
args: ~w ( --load-path=../deps css/app.scss ../priv/static/assets/app.css )
]
Então em app.scss
:
@import " phoenix_pages/css/monokai " ;
Instale o plugin postcss-import
conforme descrito aqui e adicione o seguinte a assets/postcss.config.js
:
module . exports = {
plugins : {
"postcss-import" : { }
}
}
Então em app.css
:
@import "../../deps/phoenix_pages/css/monokai" ;
Para criar uma página de índice com links para todas as outras páginas, crie uma rota GET normal e use a opção id
junto com get_pages/1
e get_pages!/1
em seu roteador:
get "/blog" , BlogController , :index
pages "/blog/:page" , BlogController , :show ,
id: :blog ,
from: "priv/blog/**/*.md" ,
attrs: [ :title , :author , :date ]
defmodule MyAppWeb.BlogController do
use MyAppWeb , :controller
def index ( conn , _params ) do
pages = MyAppWeb.Router . get_pages! ( :blog )
conn
|> assign ( :pages , pages )
|> render ( "index.html" )
end
def show ( conn , _params ) do
render ( conn , "show.html" )
end
end
<.link :for={page < - @pages} navigate = {page.path} >
<%= page.assigns.title % >
</.link>
Todos os arquivos de página são lidos e armazenados em cache durante a compilação, portanto, as funções get_pages
não lerão nada do sistema de arquivos, o que os torna muito eficientes.
As páginas retornadas das funções get_pages
serão classificadas por nome de arquivo. Se você quiser especificar uma ordem diferente durante a compilação em vez de no controlador em cada carregamento de página, use a opção sort
:
pages "/blog/:page" , BlogController , :show ,
id: :blog ,
from: "priv/blog/**/*.md" ,
attrs: [ :title , :author , :date ] ,
sort: { :date , :desc }
Qualquer valor de atributo do frontmatter pode ser definido como o valor de classificação.
Ao definir o caminho das páginas, o segmento :page
será substituído para cada página gerada durante a compilação pelos valores derivados de **
e *
. Isso é diferente dos segmentos em rotas regulares, que são analisados durante o tempo de execução no atributo params
da função do controlador.
Por exemplo, digamos que você tenha a seguinte estrutura de arquivos:
┌── priv/
│ ┌── pages/
│ │ ┌── foo.md
│ │ ├── bar/
│ │ │ ┌── baz.md
Definir pages "/:page", from: "priv/pages/**/*.md"
em seu roteador criará duas rotas: get "/foo"
e get "/bar/baz"
. Você pode até colocar o segmento :page
em algum outro lugar no caminho, como /blog/:page
, e ele funcionará conforme o esperado criando get "/blog/foo"
e get "/blog/bar/baz"
.
Para cenários complexos, você tem a opção de usar variáveis de grupo de captura em vez do segmento :page
.
Digamos que você tenha a mesma estrutura de arquivo acima, mas não deseja que o caminho baz
seja aninhado em /bar
. Você pode definir pages "/$2", from: "priv/pages/**/*.md"
, usando $2
em vez de :page
. Isso criará duas rotas: get "/foo"
e get "/bar"
.
As variáveis do grupo de captura conterão o valor dos pedaços **
e *
em ordem, começando em $1
. Lembre-se de que **
corresponderá a todos os arquivos e a zero ou mais diretórios e subdiretórios, e *
corresponderá a qualquer número de caracteres até o final do nome do arquivo, o próximo ponto ou a próxima barra.
Para obter mais informações sobre os padrões curinga, consulte Path.wildcard/2.
Além das opções de redução personalizáveis, a renderização de redução também oferece suporte a atributos IAL por padrão. Isso significa que você pode adicionar atributos HTML a qualquer elemento de nível de bloco usando a sintaxe {:attr}
.
Por exemplo, para criar uma saída renderizada de <h1 class="foobar">Header</h1>
:
# Header{:.foobar}
Os atributos podem ser um dos seguintes:
{:#id}
para definir um ID{:.className}
para definir um nome de classe{:name=value}
, {:name="value"}
ou {:name='value'}
para definir qualquer outro atributo Para definir vários atributos, separe-os com espaços: {:#id name=value}
.
Se você adicionar, remover ou alterar páginas enquanto executa mix phx.server
, elas serão automaticamente substituídas no cache e você não precisará reiniciar para que tenham efeito. Para recarregar ao vivo quando uma página muda, adicione à lista de padrões da configuração do Endpoint em config/dev.exs
:
config :myapp , MyAppWeb.Endpoint ,
live_reload: [
patterns: [
# ...
~r " priv/pages/.*(md)$ " ,
# ...
]
]