Добавляйте блоги, документацию и другие статические страницы в приложения Phoenix. Эта библиотека легко интегрируется с вашим маршрутизатором и имеет встроенную поддержку рендеринга уценки с помощью frontmatter, подсветки синтаксиса, кэширования во время компиляции и многого другого.
def deps do
[
{ :phoenix_pages , "~> 0.1" }
]
end
Рекомендуемый способ установки в ваше приложение Phoenix — добавить это в функцию router
в lib/myapp_web.ex
, заменив myapp
именем вашего приложения:
def router do
quote do
use Phoenix.Router , helpers: false
use PhoenixPages , otp_app: :myapp
# ...
end
end
Теперь вы можете добавить новый маршрут с помощью макроса pages/4
:
scope "/" , MyAppWeb do
pipe_through :browser
get "/" , PageController , :home
pages "/:page" , PageController , :show , from: "priv/pages/**/*.md"
end
Это прочитает все файлы уценки из priv/pages
и создаст новый маршрут GET для каждого из них. Сегмент :page
будет заменен путем и именем файла (без расширения) относительно базового каталога (см. Определение путей).
Вам также потребуется добавить обработчик :show
в 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
Наконец, добавьте шаблон в lib/myapp_web/controllers/page_html/show.html.heex
. Отрисованная уценка страницы будет доступна в назначении inner_content
:
< main >
<%= @inner_content % >
</ main >
Вот и все! Теперь попробуйте создать файл priv/pages/hello.md
и посетить /hello
.
Чтобы запретить добавление скобок в макрос pages
mix format
, как и в других макросах Phoenix Router, добавьте :phoenix_pages
в .formatter.exs
:
[
import_deps: [ :ecto , :ecto_sql , :phoenix , :phoenix_pages ]
]
Frontmatter позволяет включать переменные, специфичные для страницы, в начало файла уценки с использованием формата YAML. Если вы устанавливаете переменные frontmatter (что необязательно), они должны быть первыми в файле и должны быть установлены между тройными пунктирными линиями:
---
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.
Чтобы указать, какие значения заголовка ожидаются на каждой странице, установите параметр attrs
:
pages "/:page" , PageController , :show ,
from: "priv/pages/**/*.md" ,
attrs: [ :title , author: nil ]
Значения атомов будут считаться обязательными, и если они отсутствуют на какой-либо из страниц, будет выдана ошибка компиляции. Пары «ключ-значение» должны идти последними в списке и будут считаться необязательными при определении значения по умолчанию. Любые значения заголовка, не определенные в списке атрибутов, будут автоматически отброшены.
Действительные значения атрибутов будут доступны в назначениях:
< main >
< h1 > <%= @title % > </ h1 >
< h2 :if = {@author} > <%= @author % > </ h2 >
<%= @inner_content % >
</ main >
Phoenix Pages использует проект Makeup для подсветки синтаксиса. Чтобы включить, добавьте лексер для вашего конкретного языка в зависимости проекта. Phoenix Pages подберет новую зависимость и начнет выделять блоки кода без какой-либо дополнительной настройки. По умолчанию лексеры не включены.
`{: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"}`
Если выбранный вами язык не поддерживается, рассмотрите возможность написания нового лексера Makeup, чтобы внести свой вклад в сообщество. В противном случае вы можете использовать подсветку синтаксиса на основе JS, например, Highlight.js, установив code_class_prefix: "language-"
и syntax_highlighting: false
в render_options
.
Затем импортируйте тему, указанную ниже, в свой пакет CSS. Особенности этого во многом зависят от вашей конфигурации CSS, но ниже приведены несколько примеров. В большинстве случаев вам потребуется импортировать phoenix_pages/css/monokai.css
(или любую другую тему, которую вы выберете) в свой пакет и убедиться, что deps
включен в качестве каталога поставщика.
Используя установщик ESBuild, добавьте параметр env
в 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 )
]
Затем в app.js
:
import "phoenix_pages/css/monokai.css" ;
Используя установщик Sass, добавьте флаг --load-path
в 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 )
]
Затем в app.scss
:
@import " phoenix_pages/css/monokai " ;
Установите плагин postcss-import
как описано здесь, и добавьте следующее в assets/postcss.config.js
:
module . exports = {
plugins : {
"postcss-import" : { }
}
}
Затем в app.css
:
@import "../../deps/phoenix_pages/css/monokai" ;
Чтобы создать индексную страницу со ссылками на все остальные страницы, создайте обычный маршрут GET и используйте параметр id
вместе с get_pages/1
и get_pages!/1
в вашем маршрутизаторе:
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>
Все файлы страниц считываются и кэшируются во время компиляции, поэтому функции get_pages
фактически ничего не читают из файловой системы, что делает их очень производительными.
Страницы, возвращаемые функциями get_pages
, будут отсортированы по имени файла. Если вы хотите указать другой порядок во время компиляции, а не в контроллере при каждой загрузке страницы, используйте опцию sort
:
pages "/blog/:page" , BlogController , :show ,
id: :blog ,
from: "priv/blog/**/*.md" ,
attrs: [ :title , :author , :date ] ,
sort: { :date , :desc }
Любое значение атрибута из заголовка может быть определено как значение сортировки.
При определении пути к страницам сегмент :page
будет заменен для каждой сгенерированной страницы во время компиляции значениями, полученными из **
и *
. Это отличается от сегментов в обычных маршрутах, которые во время выполнения анализируются в атрибуте params
функции контроллера.
Например, предположим, что у вас есть следующая файловая структура:
┌── priv/
│ ┌── pages/
│ │ ┌── foo.md
│ │ ├── bar/
│ │ │ ┌── baz.md
Определение pages "/:page", from: "priv/pages/**/*.md"
в вашем маршрутизаторе создаст два маршрута: get "/foo"
и get "/bar/baz"
. Вы даже можете поместить сегмент :page
в другое место пути, например /blog/:page
, и он будет работать как положено, создавая get "/blog/foo"
и get "/blog/bar/baz"
.
В сложных сценариях у вас есть возможность использовать переменные группы захвата вместо сегмента :page
.
Допустим, у вас та же файловая структура, что и выше, но вы не хотите, чтобы путь baz
был вложен в /bar
. Вы можете определить pages "/$2", from: "priv/pages/**/*.md"
, используя $2
вместо :page
. Это создаст два маршрута: get "/foo"
и get "/bar"
.
Переменные группы захвата будут содержать значения фрагментов **
и *
по порядку, начиная с $1
. Имейте в виду, что **
будет соответствовать всем файлам и нулю или более каталогам и подкаталогам, а *
будет соответствовать любому количеству символов до конца имени файла, следующей точки или следующей косой черты.
Для получения дополнительной информации о шаблонах подстановочных знаков посетите Path.wildcard/2.
В дополнение к настраиваемым параметрам уценки рендеринг уценки также по умолчанию поддерживает атрибуты IAL. Это означает, что вы можете добавлять атрибуты HTML к любому элементу уровня блока, используя синтаксис {:attr}
.
Например, чтобы создать визуализированный вывод <h1 class="foobar">Header</h1>
:
# Header{:.foobar}
Атрибуты могут быть одним из следующих:
{:#id}
для определения идентификатора{:.className}
для определения имени класса{:name=value}
, {:name="value"}
или {:name='value'}
для определения любого другого атрибута. Чтобы определить несколько атрибутов, разделите их пробелами: {:#id name=value}
.
Если вы добавите, удалите или измените страницы во время работы mix phx.server
, они будут автоматически заменены в кеше, и вам не придется перезапускать страницы, чтобы они вступили в силу. Чтобы выполнить перезагрузку при изменении страницы, добавьте в список шаблонов конфигурации конечной точки в config/dev.exs
:
config :myapp , MyAppWeb.Endpoint ,
live_reload: [
patterns: [
# ...
~r " priv/pages/.*(md)$ " ,
# ...
]
]