Fügen Sie Blogs, Dokumentationen und andere statische Seiten zu Phoenix-Apps hinzu. Diese Bibliothek lässt sich nahtlos in Ihren Router integrieren und bietet integrierte Unterstützung für das Rendern von Markdown mit Frontmatter, Syntaxhervorhebung, Caching zur Kompilierungszeit und mehr.
def deps do
[
{ :phoenix_pages , "~> 0.1" }
]
end
Die empfohlene Methode zur Installation in Ihrer Phoenix-Anwendung besteht darin, dies zu Ihrer router
Funktion in lib/myapp_web.ex
hinzuzufügen und dabei myapp
durch den Namen Ihrer Anwendung zu ersetzen:
def router do
quote do
use Phoenix.Router , helpers: false
use PhoenixPages , otp_app: :myapp
# ...
end
end
Jetzt können Sie mit dem Makro pages/4
eine neue Route hinzufügen:
scope "/" , MyAppWeb do
pipe_through :browser
get "/" , PageController , :home
pages "/:page" , PageController , :show , from: "priv/pages/**/*.md"
end
Dadurch werden alle Markdown-Dateien von priv/pages
gelesen und für jede einzelne eine neue GET-Route erstellt. Das :page
-Segment wird durch den Pfad und Dateinamen (ohne Erweiterung) relativ zum Basisverzeichnis ersetzt (siehe Definieren von Pfaden).
Sie müssen außerdem den :show
Handler zu lib/myapp_web/controllers/page_controller.ex
hinzufügen:
defmodule MyAppWeb.PageController do
use MyAppWeb , :controller
# ...
def show ( conn , _params ) do
render ( conn , "show.html" )
end
end
Fügen Sie abschließend eine Vorlage unter lib/myapp_web/controllers/page_html/show.html.heex
hinzu. Der gerenderte Markdown der Seite ist in der inner_content
-Zuweisung verfügbar:
< main >
<%= @inner_content % >
</ main >
Das ist es! Versuchen Sie nun, eine Datei unter priv/pages/hello.md
zu erstellen und /hello
aufzurufen.
Um zu verhindern, dass mix format
Klammern zum pages
hinzufügt, ähnlich wie bei den anderen Phoenix Router-Makros, fügen Sie :phoenix_pages
zu .formatter.exs
hinzu:
[
import_deps: [ :ecto , :ecto_sql , :phoenix , :phoenix_pages ]
]
Mit Frontmatter können seitenspezifische Variablen mithilfe des YAML-Formats oben in eine Markdown-Datei eingefügt werden. Wenn Sie Frontmatter-Variablen festlegen (was optional ist), müssen diese an erster Stelle in der Datei stehen und zwischen dreifach gestrichelten Linien festgelegt werden:
---
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.
Um anzugeben, welche Frontmatter-Werte auf jeder Seite erwartet werden, legen Sie die Option attrs
fest:
pages "/:page" , PageController , :show ,
from: "priv/pages/**/*.md" ,
attrs: [ :title , author: nil ]
Atomwerte gelten als erforderlich und es wird ein Kompilierungsfehler ausgegeben, wenn sie auf einer der Seiten fehlen. Schlüsselwerte müssen in der Liste an letzter Stelle stehen und werden durch die Definition eines Standardwerts als optional betrachtet. Alle Frontmatter-Werte, die nicht in der Attributliste definiert sind, werden stillschweigend verworfen.
Gültige Attributwerte sind in den Zuweisungen verfügbar:
< main >
< h1 > <%= @title % > </ h1 >
< h2 :if = {@author} > <%= @author % > </ h2 >
<%= @inner_content % >
</ main >
Phoenix Pages verwendet das Makeup-Projekt zur Syntaxhervorhebung. Fügen Sie zum Aktivieren einen Lexer für Ihre spezifische(n) Sprache(n) zu den Projektabhängigkeiten hinzu. Phoenix Pages übernimmt die neue Abhängigkeit und beginnt ohne weitere Konfiguration mit der Hervorhebung Ihrer Codeblöcke. Standardmäßig sind keine Lexer enthalten.
`{: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"}`
Wenn die Sprache Ihrer Wahl nicht unterstützt wird, sollten Sie erwägen, einen neuen Makeup-Lexer zu schreiben, um einen Beitrag zur Community zu leisten. Andernfalls können Sie einen JS-basierten Syntax-Highlighter wie highlights.js verwenden, indem Sie in render_options
code_class_prefix: "language-"
und syntax_highlighting: false
festlegen.
Importieren Sie als Nächstes ein unten aufgeführtes Theme in Ihr CSS-Bundle. Die Einzelheiten dazu hängen stark von Ihrer CSS-Konfiguration ab, aber einige Beispiele sind unten aufgeführt. In den meisten Fällen müssen Sie phoenix_pages/css/monokai.css
(oder ein beliebiges Theme Ihrer Wahl) in Ihr Bundle importieren und sicherstellen, dass deps
als Anbieterverzeichnis enthalten ist.
Fügen Sie mit dem ESBuild-Installationsprogramm die Option env
zu config/config.exs
hinzu:
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 )
]
Dann in app.js
:
import "phoenix_pages/css/monokai.css" ;
Fügen Sie mit dem Sass-Installationsprogramm das Flag --load-path
zu config/config.exs
hinzu:
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 )
]
Dann in app.scss
:
@import " phoenix_pages/css/monokai " ;
Installieren Sie das Plugin postcss-import
wie hier beschrieben und fügen Sie Folgendes zu assets/postcss.config.js
hinzu:
module . exports = {
plugins : {
"postcss-import" : { }
}
}
Dann in app.css
:
@import "../../deps/phoenix_pages/css/monokai" ;
Um eine Indexseite mit Links zu allen anderen Seiten zu erstellen, erstellen Sie eine normale GET-Route und verwenden Sie die Option id
neben get_pages/1
und get_pages!/1
in Ihrem Router:
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>
Alle Seitendateien werden während der Kompilierung gelesen und zwischengespeichert, sodass die get_pages
-Funktionen eigentlich nichts aus dem Dateisystem lesen – was sie sehr leistungsfähig macht.
Die von den get_pages
-Funktionen zurückgegebenen Seiten werden nach Dateinamen sortiert. Wenn Sie bei der Kompilierung und nicht im Controller bei jedem Laden einer Seite eine andere Reihenfolge angeben möchten, verwenden Sie die sort
:
pages "/blog/:page" , BlogController , :show ,
id: :blog ,
from: "priv/blog/**/*.md" ,
attrs: [ :title , :author , :date ] ,
sort: { :date , :desc }
Als Sortierwert kann jeder Attributwert aus der Frontmatter definiert werden.
Beim Definieren des Seitenpfads wird das :page
Segment für jede generierte Seite während der Kompilierung durch die von **
und *
abgeleiteten Werte ersetzt. Dies unterscheidet sich von Segmenten in regulären Routen, die zur Laufzeit in das params
-Attribut der Controller-Funktion geparst werden.
Angenommen, Sie haben die folgende Dateistruktur:
┌── priv/
│ ┌── pages/
│ │ ┌── foo.md
│ │ ├── bar/
│ │ │ ┌── baz.md
Durch das Definieren pages "/:page", from: "priv/pages/**/*.md"
in Ihrem Router werden zwei Routen erstellt: get "/foo"
und get "/bar/baz"
. Sie können das :page
Segment sogar an einer anderen Stelle im Pfad platzieren, z. B. /blog/:page
, und es funktioniert wie erwartet, indem Sie get "/blog/foo"
und get "/blog/bar/baz"
erstellen.
Für komplexe Szenarien haben Sie die Möglichkeit, Capture-Gruppenvariablen anstelle des :page
-Segments zu verwenden.
Nehmen wir an, Sie haben die gleiche Dateistruktur wie oben, möchten aber nicht, dass der baz
-Pfad unter /bar
verschachtelt wird. Sie könnten pages "/$2", from: "priv/pages/**/*.md"
definieren und dabei $2
anstelle von :page
verwenden. Dadurch werden zwei Routen erstellt: get "/foo"
und get "/bar"
.
Capture-Gruppenvariablen enthalten den Wert der **
und *
-Blöcke der Reihe nach, beginnend bei $1
. Beachten Sie, dass **
mit allen Dateien und null oder mehr Verzeichnissen und Unterverzeichnissen übereinstimmt und *
mit einer beliebigen Anzahl von Zeichen bis zum Ende des Dateinamens, dem nächsten Punkt oder dem nächsten Schrägstrich übereinstimmt.
Weitere Informationen zu den Platzhaltermustern finden Sie unter Path.wildcard/2.
Zusätzlich zu den anpassbaren Markdown-Optionen unterstützt das Markdown-Rendering standardmäßig auch IAL-Attribute. Das heißt, Sie können HTML-Attribute zu jedem Element auf Blockebene hinzufügen, indem Sie die Syntax {:attr}
verwenden.
So erstellen Sie beispielsweise eine gerenderte Ausgabe von <h1 class="foobar">Header</h1>
:
# Header{:.foobar}
Attribute können eines der folgenden sein:
{:#id}
um eine ID zu definieren{:.className}
um einen Klassennamen zu definieren{:name=value}
, {:name="value"}
oder {:name='value'}
um ein anderes Attribut zu definieren Um mehrere Attribute zu definieren, trennen Sie sie durch Leerzeichen: {:#id name=value}
.
Wenn Sie Seiten hinzufügen, entfernen oder ändern, während mix phx.server
ausgeführt wird, werden diese automatisch im Cache ersetzt und Sie müssen nicht neu starten, damit sie wirksam werden. Um ein Live-Neuladen zu ermöglichen, wenn sich eine Seite ändert, fügen Sie Folgendes zur Musterliste der Endpoint-Konfiguration in config/dev.exs
hinzu:
config :myapp , MyAppWeb.Endpoint ,
live_reload: [
patterns: [
# ...
~r " priv/pages/.*(md)$ " ,
# ...
]
]