Redo.
This commit is contained in:
parent
7918707dd9
commit
72fd3351b3
18
Cargo.toml
18
Cargo.toml
|
@ -7,25 +7,13 @@ version = "1.0.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
repository = "https://git.holllo.cc/Bauke/bauke.xyz"
|
repository = "https://git.holllo.cc/Bauke/bauke.xyz"
|
||||||
license = "AGPL-3.0-or-later"
|
license = "AGPL-3.0-or-later"
|
||||||
build = "source/build.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "bauke-xyz"
|
name = "bauke-xyz"
|
||||||
path = "source/main.rs"
|
path = "source/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
gloo-console = "0.2.0"
|
askama = "0.10.5"
|
||||||
gloo-timers = "0.2.1"
|
color-eyre = "0.5.11"
|
||||||
log = "0.4.14"
|
rsass = "0.22.2"
|
||||||
rand = "0.8.4"
|
|
||||||
userstyles = { git = "https://git.holllo.cc/Bauke/userstyles" }
|
|
||||||
wasm-logger = "0.2.0"
|
|
||||||
yew = "0.18.0"
|
|
||||||
yew-router = "0.15.0"
|
|
||||||
|
|
||||||
[dependencies.getrandom]
|
|
||||||
version = "0.2.3"
|
|
||||||
features = ["js"]
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
userstyles = { git = "https://git.holllo.cc/Bauke/userstyles" }
|
userstyles = { git = "https://git.holllo.cc/Bauke/userstyles" }
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
[build]
|
|
||||||
dist = "public"
|
|
||||||
|
|
||||||
[serve]
|
|
||||||
no_autoreload = false
|
|
||||||
port = 5500
|
|
||||||
|
|
||||||
[clean]
|
|
||||||
dist = "public"
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
[general]
|
||||||
|
dirs = ["source/templates"]
|
21
index.html
21
index.html
|
@ -1,21 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<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>bauke.xyz</title>
|
|
||||||
<link data-trunk rel="sass" href="source/scss/modern-normalize.scss" />
|
|
||||||
<link data-trunk rel="sass" href="source/scss/index.scss" />
|
|
||||||
<link data-trunk rel="copy-file" href="source/netlify/_redirects" />
|
|
||||||
<link data-trunk rel="copy-dir" href="target/userstyles" />
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<noscript>
|
|
||||||
This website requires JavaScript and WebAssembly to work.
|
|
||||||
</noscript>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
|
@ -3,7 +3,7 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"deploy": "trunk clean && cargo build -q && trunk build --release && yarn deploy:netlify",
|
"deploy": "cargo run --release -q && yarn deploy:netlify",
|
||||||
"deploy:netlify": "netlify deploy --prod --dir 'public/' -s bauke.xyz",
|
"deploy:netlify": "netlify deploy --prod --dir 'public/' -s bauke.xyz",
|
||||||
"test": "stylelint 'source/**/*.scss'"
|
"test": "stylelint 'source/**/*.scss'"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
/// Build script for the website.
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
println!("cargo:rerun-if-changed=source/**");
|
|
||||||
let build_dir = std::path::PathBuf::from("target");
|
|
||||||
|
|
||||||
for target in userstyles::ALL_USERSTYLES {
|
|
||||||
let style = userstyles::Userstyle::load(target).unwrap();
|
|
||||||
let style_name = style.metadata.name.to_lowercase().replace(" ", "-");
|
|
||||||
|
|
||||||
let style_dir = build_dir.join("userstyles");
|
|
||||||
std::fs::create_dir_all(&style_dir).unwrap();
|
|
||||||
|
|
||||||
let style_file = style_dir.join(format!("{}.user.css", style_name));
|
|
||||||
let formatted = style.format();
|
|
||||||
std::fs::write(style_file, formatted).unwrap();
|
|
||||||
|
|
||||||
if let Some(image) = style.image {
|
|
||||||
let image_file = style_dir.join(format!("{}.png", style_name));
|
|
||||||
std::fs::write(image_file, image).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
/// Contains the main page `<footer>` component.
|
|
||||||
mod page_footer;
|
|
||||||
/// Contains the main page `<header>` component.
|
|
||||||
mod page_header;
|
|
||||||
/// Contains the main page `<main>` component.
|
|
||||||
mod page_main;
|
|
||||||
|
|
||||||
pub(crate) use page_footer::PageFooter;
|
|
||||||
pub(crate) use page_header::PageHeader;
|
|
||||||
pub(crate) use page_main::PageMain;
|
|
|
@ -1,29 +0,0 @@
|
||||||
use yew::prelude::*;
|
|
||||||
|
|
||||||
/// The main page `<footer>` element.
|
|
||||||
pub(crate) struct PageFooter;
|
|
||||||
|
|
||||||
impl Component for PageFooter {
|
|
||||||
type Message = ();
|
|
||||||
type Properties = ();
|
|
||||||
|
|
||||||
fn create(_props: Self::Properties, _link: ComponentLink<Self>) -> Self {
|
|
||||||
log::trace!("Creating PageFooter");
|
|
||||||
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
|
||||||
html! {
|
|
||||||
<footer class="page-footer"></footer>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
use yew::prelude::*;
|
|
||||||
|
|
||||||
/// The main page `<header>` element.
|
|
||||||
pub(crate) struct PageHeader;
|
|
||||||
|
|
||||||
impl Component for PageHeader {
|
|
||||||
type Message = ();
|
|
||||||
type Properties = ();
|
|
||||||
|
|
||||||
fn create(_props: Self::Properties, _link: ComponentLink<Self>) -> Self {
|
|
||||||
log::trace!("Creating PageHeader");
|
|
||||||
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
|
||||||
html! {
|
|
||||||
<header class="page-header">
|
|
||||||
<h1>{"bauke.xyz"}</h1>
|
|
||||||
</header>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
use yew::prelude::*;
|
|
||||||
|
|
||||||
/// The main page `<main>` element.
|
|
||||||
pub(crate) struct PageMain;
|
|
||||||
|
|
||||||
impl Component for PageMain {
|
|
||||||
type Message = ();
|
|
||||||
type Properties = ();
|
|
||||||
|
|
||||||
fn create(_props: Self::Properties, _link: ComponentLink<Self>) -> Self {
|
|
||||||
log::trace!("Creating PageMain");
|
|
||||||
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
|
||||||
html! {
|
|
||||||
<main class="page-main">
|
|
||||||
<div class="contact-links">
|
|
||||||
<a target="_blank" href="mailto:me@bauke.xyz">
|
|
||||||
{"me@bauke.xyz"}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a target="_blank" href="https://matrix.to/#/@baukexyz:matrix.org">
|
|
||||||
{"@baukexyz:matrix.org"}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a target="_blank" href="https://mastodon.social/@bauke">
|
|
||||||
{"@bauke@mastodon.social"}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
146
source/main.rs
146
source/main.rs
|
@ -1,72 +1,106 @@
|
||||||
#![forbid(unsafe_code)]
|
use std::{fs, path::PathBuf, process::Command};
|
||||||
#![warn(missing_docs, clippy::missing_docs_in_private_items)]
|
|
||||||
|
|
||||||
//! # [bauke.xyz](https://bauke.xyz)
|
use askama::Template;
|
||||||
|
|
||||||
use yew::prelude::*;
|
mod templates;
|
||||||
use yew_router::router::Router;
|
|
||||||
|
|
||||||
/// Components collection.
|
fn main() -> color_eyre::Result<()> {
|
||||||
pub(crate) mod components;
|
color_eyre::install()?;
|
||||||
/// Routes collection.
|
|
||||||
pub(crate) mod routes;
|
|
||||||
|
|
||||||
/// All routes.
|
let build_dir = PathBuf::from("target");
|
||||||
#[derive(Clone, yew_router::Switch)]
|
|
||||||
pub(crate) enum Route {
|
for target in userstyles::ALL_USERSTYLES {
|
||||||
#[to = "/userstyles"]
|
let style = userstyles::Userstyle::load(target)?;
|
||||||
Userstyles,
|
let style_name = style.metadata.name.to_lowercase().replace(" ", "-");
|
||||||
#[to = "/{}"]
|
|
||||||
NotFound(String),
|
let style_dir = build_dir.join("userstyles");
|
||||||
#[to = "/"]
|
fs::create_dir_all(&style_dir)?;
|
||||||
Home,
|
|
||||||
|
let style_file = style_dir.join(format!("{}.user.css", style_name));
|
||||||
|
let formatted = style.format();
|
||||||
|
fs::write(style_file, formatted)?;
|
||||||
|
|
||||||
|
if let Some(image) = style.image {
|
||||||
|
let image_file = style_dir.join(format!("{}.png", style_name));
|
||||||
|
fs::write(image_file, image)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The main component.
|
let public_dir = PathBuf::from("public");
|
||||||
pub(crate) struct Model;
|
|
||||||
|
|
||||||
impl Component for Model {
|
let source_dir = PathBuf::from("source");
|
||||||
type Message = ();
|
|
||||||
type Properties = ();
|
|
||||||
|
|
||||||
fn create(_props: Self::Properties, _link: ComponentLink<Self>) -> Self {
|
let styles = userstyles::ALL_USERSTYLES
|
||||||
Self
|
.iter()
|
||||||
|
.map(|target| userstyles::Userstyle::load(target))
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let templates_to_render: Vec<(Box<dyn Template>, PathBuf)> = vec![
|
||||||
|
(
|
||||||
|
Box::new(templates::Index {
|
||||||
|
page_title: "bauke.xyz".to_string(),
|
||||||
|
}),
|
||||||
|
public_dir.join("index.html"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Box::new(templates::Userstyles {
|
||||||
|
page_title: "bauke.xyz".to_string(),
|
||||||
|
styles,
|
||||||
|
}),
|
||||||
|
public_dir.join("userstyles/index.html"),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (template, path) in templates_to_render {
|
||||||
|
fs::create_dir_all(&path.parent().unwrap())?;
|
||||||
|
let rendered = template.render()?;
|
||||||
|
fs::write(path, rendered)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
|
let css_dir = public_dir.join("css");
|
||||||
unimplemented!()
|
fs::create_dir_all(&css_dir)?;
|
||||||
}
|
|
||||||
|
|
||||||
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
|
let scss_dir = source_dir.join("scss");
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
let scss_to_render = vec![
|
||||||
html! {
|
(scss_dir.join("index.scss"), css_dir.join("index.css")),
|
||||||
<Router<Route, ()>
|
(
|
||||||
render = Router::render(|route: Route| {
|
scss_dir.join("modern-normalize.scss"),
|
||||||
match route {
|
css_dir.join("modern-normalize.css"),
|
||||||
Route::NotFound(_) => html! {
|
),
|
||||||
<main class="error-404">
|
];
|
||||||
<p>{"🤷"}</p>
|
|
||||||
</main>
|
for (source, destination) in scss_to_render {
|
||||||
|
let rendered = rsass::compile_scss_path(
|
||||||
|
&source,
|
||||||
|
rsass::output::Format {
|
||||||
|
style: rsass::output::Style::Expanded,
|
||||||
|
precision: 5,
|
||||||
},
|
},
|
||||||
Route::Home => html! {
|
)?;
|
||||||
<routes::HomeRoute />
|
|
||||||
},
|
fs::write(destination, rendered)?;
|
||||||
Route::Userstyles => html! {
|
|
||||||
<routes::UserstylesRoute />
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Our main function.
|
let files_to_copy = vec![(
|
||||||
pub(crate) fn main() {
|
source_dir.join("netlify/_redirects"),
|
||||||
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
public_dir.join("_redirects"),
|
||||||
log::debug!("Initializing Yew");
|
)];
|
||||||
yew::start_app::<Model>();
|
|
||||||
|
for (source, destination) in files_to_copy {
|
||||||
|
fs::copy(source, destination)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dirs_to_copy = vec![(build_dir.join("userstyles"), public_dir)];
|
||||||
|
|
||||||
|
for (source, destination) in dirs_to_copy {
|
||||||
|
Command::new("cp")
|
||||||
|
.arg("-r")
|
||||||
|
.arg(source)
|
||||||
|
.arg(destination)
|
||||||
|
.output()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
use yew::prelude::*;
|
|
||||||
|
|
||||||
use crate::components::{PageFooter, PageHeader, PageMain};
|
|
||||||
|
|
||||||
/// The route for `/`.
|
|
||||||
pub(crate) struct HomeRoute;
|
|
||||||
|
|
||||||
impl Component for HomeRoute {
|
|
||||||
type Message = ();
|
|
||||||
type Properties = ();
|
|
||||||
|
|
||||||
fn create(_props: Self::Properties, _link: ComponentLink<Self>) -> Self {
|
|
||||||
log::trace!("Creating HomeRoute");
|
|
||||||
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
|
||||||
html! {
|
|
||||||
<>
|
|
||||||
<PageHeader />
|
|
||||||
<PageMain />
|
|
||||||
<PageFooter />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
/// The route for `/`.
|
|
||||||
mod home;
|
|
||||||
/// The route for `/userstyles`.
|
|
||||||
mod userstyles;
|
|
||||||
|
|
||||||
pub(crate) use self::userstyles::UserstylesRoute;
|
|
||||||
pub(crate) use home::HomeRoute;
|
|
|
@ -1,57 +0,0 @@
|
||||||
use yew::prelude::*;
|
|
||||||
|
|
||||||
use crate::components::PageHeader;
|
|
||||||
|
|
||||||
/// The route for `/userstyles`.
|
|
||||||
pub(crate) struct UserstylesRoute;
|
|
||||||
|
|
||||||
impl Component for UserstylesRoute {
|
|
||||||
type Message = ();
|
|
||||||
type Properties = ();
|
|
||||||
|
|
||||||
fn create(_props: Self::Properties, _link: ComponentLink<Self>) -> Self {
|
|
||||||
log::trace!("Creating UserstylesRoute");
|
|
||||||
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self) -> Html {
|
|
||||||
let styles = userstyles::ALL_USERSTYLES
|
|
||||||
.iter()
|
|
||||||
.map(|target| userstyles::Userstyle::load(target))
|
|
||||||
.flatten()
|
|
||||||
.map(|style| {
|
|
||||||
let style_name = style.metadata.name.to_lowercase().replace(" ", "-");
|
|
||||||
|
|
||||||
html! {
|
|
||||||
<div class="style">
|
|
||||||
<div class="header">
|
|
||||||
<img src=format!("/userstyles/{}.png", style_name) />
|
|
||||||
<h2>{style.metadata.name}</h2>
|
|
||||||
<a target="_blank" href={style.metadata.update_url}>{"Install"}</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p>{style.metadata.description}</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
html! {
|
|
||||||
<>
|
|
||||||
<PageHeader />
|
|
||||||
<main class="page-main userstyles">
|
|
||||||
{styles}
|
|
||||||
</main>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
|
border-color: #fff;
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<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>{{ page_title }}</title>
|
||||||
|
<link rel="stylesheet" href="/css/modern-normalize.css">
|
||||||
|
{% block head %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{% block body %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,27 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<link rel="stylesheet" href="/css/index.css">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<header class="page-header">
|
||||||
|
<h1>bauke.xyz</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="page-main">
|
||||||
|
<div class="contact-links">
|
||||||
|
<a target="_blank" href="mailto:me@bauke.xyz">
|
||||||
|
me@bauke.xyz
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a target="_blank" href="https://matrix.to/#/@baukexyz:matrix.org">
|
||||||
|
@baukexyz:matrix.org
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a target="_blank" href="https://mastodon.social/@bauke">
|
||||||
|
@bauke@mastodon.social
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,14 @@
|
||||||
|
use askama::Template;
|
||||||
|
|
||||||
|
#[derive(Debug, Template)]
|
||||||
|
#[template(path = "index.html")]
|
||||||
|
pub struct Index {
|
||||||
|
pub page_title: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Template)]
|
||||||
|
#[template(path = "userstyles.html")]
|
||||||
|
pub struct Userstyles {
|
||||||
|
pub page_title: String,
|
||||||
|
pub styles: Vec<userstyles::Userstyle>,
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
<link rel="stylesheet" href="/css/index.css">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<header class="page-header">
|
||||||
|
<h1>bauke.xyz</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main class="page-main userstyles">
|
||||||
|
{% for style in styles %}
|
||||||
|
<div class="style">
|
||||||
|
<div class="header">
|
||||||
|
<img src="{{ format!("/userstyles/{}.png", style.metadata.name.to_lowercase().replace(" ", "-"))|safe }}" />
|
||||||
|
<h2>{{ style.metadata.name }}</h2>
|
||||||
|
<a target="_blank" href="{{ style.metadata.update_url|safe }}">Install</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>{{ style.metadata.description }}</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</main>
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue