This commit is contained in:
parent
b8ed8da199
commit
696b34f2f1
4 changed files with 669 additions and 2 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,3 +3,4 @@ src/htmx.min.js
|
|||
src/flowbite.min.css
|
||||
src/flowbite.min.js
|
||||
src/material.woff2
|
||||
/examples/test.rs
|
|
@ -6,6 +6,7 @@ use tokio::sync::OnceCell;
|
|||
pub mod asset;
|
||||
pub mod auth;
|
||||
pub mod format;
|
||||
pub mod ogp;
|
||||
pub mod request;
|
||||
pub mod result;
|
||||
pub mod ui;
|
||||
|
|
656
src/ogp.rs
Normal file
656
src/ogp.rs
Normal file
|
@ -0,0 +1,656 @@
|
|||
use maud::{PreEscaped, html};
|
||||
|
||||
use crate::ui::prelude::Optional;
|
||||
|
||||
// TODO : documentation
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Meta(key: &str, value: &str) -> PreEscaped<String> {
|
||||
html! {
|
||||
meta property=(key) content=(value) {};
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Metadata {
|
||||
pub title: String,
|
||||
pub kind: PreEscaped<String>,
|
||||
pub image: MediaItem,
|
||||
pub url: String,
|
||||
|
||||
pub audio: Option<MediaItem>,
|
||||
pub description: Option<String>,
|
||||
pub determiner: Option<Determiner>,
|
||||
pub locale: Option<String>,
|
||||
pub locale_alternate: Vec<String>,
|
||||
pub site_name: Option<String>,
|
||||
pub video: Option<MediaItem>,
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
pub fn new<T: ObjectType>(url: &str, title: &str, image: MediaItem, kind: T) -> Self {
|
||||
Self {
|
||||
title: title.to_string(),
|
||||
kind: kind.render(),
|
||||
image,
|
||||
url: url.to_string(),
|
||||
audio: None,
|
||||
description: None,
|
||||
determiner: None,
|
||||
locale: None,
|
||||
locale_alternate: Vec::new(),
|
||||
site_name: None,
|
||||
video: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn audio(mut self, audio: MediaItem) -> Self {
|
||||
self.audio = Some(audio);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn description(mut self, description: &str) -> Self {
|
||||
self.description = Some(description.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn determiner(mut self, determiner: Determiner) -> Self {
|
||||
self.determiner = Some(determiner);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn locale(mut self, locale: &str) -> Self {
|
||||
self.locale = Some(locale.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn locale_alternate(mut self, locale: &str) -> Self {
|
||||
self.locale_alternate.push(locale.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn site_name(mut self, site_name: &str) -> Self {
|
||||
self.site_name = Some(site_name.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn video(mut self, video: MediaItem) -> Self {
|
||||
self.video = Some(video);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn array_meta(v: &[String], tag_name: &str) -> PreEscaped<String> {
|
||||
let r = v.into_iter().map(|x| Meta(tag_name, x)).collect::<Vec<_>>();
|
||||
html! {
|
||||
@for e in r {
|
||||
(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Determiner {
|
||||
A,
|
||||
An,
|
||||
The,
|
||||
Blank,
|
||||
Auto,
|
||||
}
|
||||
|
||||
impl Determiner {
|
||||
pub fn render(&self) -> PreEscaped<String> {
|
||||
match self {
|
||||
Determiner::A => Meta("og:determiner", "a"),
|
||||
Determiner::An => Meta("og:determiner", "an"),
|
||||
Determiner::The => Meta("og:determiner", "the"),
|
||||
Determiner::Blank => Meta("og:determiner", ""),
|
||||
Determiner::Auto => Meta("og:determiner", "auto"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Metadata {
|
||||
pub fn render(&self) -> PreEscaped<String> {
|
||||
html! {
|
||||
(Meta("og:title", &self.title))
|
||||
(Optional(self.description.as_ref(), |desc| Meta("og:description", desc)))
|
||||
(Optional(self.site_name.as_ref(), |site_name| Meta("og:site_name", site_name)))
|
||||
(Optional(self.determiner.as_ref(), |determiner| determiner.render()))
|
||||
(Optional(self.locale.as_ref(), |locale| Meta("og:locale", locale)))
|
||||
(array_meta(&self.locale_alternate, "og:locale:alternate"))
|
||||
(Meta("og:url", &self.url))
|
||||
(self.image.render())
|
||||
(Optional(self.video.as_ref(), |video| video.render()))
|
||||
(Optional(self.audio.as_ref(), |audio| audio.render()))
|
||||
(self.kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MediaItemKind {
|
||||
Image,
|
||||
Video,
|
||||
Audio,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MediaItem {
|
||||
pub url: String,
|
||||
pub secure_url: Option<String>,
|
||||
pub mime: String,
|
||||
pub width: Option<u32>,
|
||||
pub height: Option<u32>,
|
||||
pub alt: String,
|
||||
pub kind: MediaItemKind,
|
||||
}
|
||||
|
||||
impl MediaItem {
|
||||
pub fn secure_url(mut self, url: &str) -> Self {
|
||||
self.secure_url = Some(url.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn width(mut self, width: u32) -> Self {
|
||||
self.width = Some(width);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn height(mut self, height: u32) -> Self {
|
||||
self.height = Some(height);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl MediaItem {
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Image(url: &str, mime: &str, alt: &str) -> Self {
|
||||
let secure_url = if url.starts_with("https") {
|
||||
Some(url.to_string())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
MediaItem {
|
||||
url: url.to_string(),
|
||||
secure_url: secure_url,
|
||||
mime: mime.to_string(),
|
||||
width: None,
|
||||
height: None,
|
||||
alt: alt.to_string(),
|
||||
kind: MediaItemKind::Image,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Video(url: &str, mime: &str, alt: &str) -> Self {
|
||||
let secure_url = if url.starts_with("https") {
|
||||
Some(url.to_string())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
MediaItem {
|
||||
url: url.to_string(),
|
||||
secure_url: secure_url,
|
||||
mime: mime.to_string(),
|
||||
width: None,
|
||||
height: None,
|
||||
alt: alt.to_string(),
|
||||
kind: MediaItemKind::Video,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Audio(url: &str, mime: &str, alt: &str) -> Self {
|
||||
let secure_url = if url.starts_with("https") {
|
||||
Some(url.to_string())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
MediaItem {
|
||||
url: url.to_string(),
|
||||
secure_url: secure_url,
|
||||
mime: mime.to_string(),
|
||||
width: None,
|
||||
height: None,
|
||||
alt: alt.to_string(),
|
||||
kind: MediaItemKind::Audio,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MediaItem {
|
||||
pub fn render(&self) -> PreEscaped<String> {
|
||||
match self.kind {
|
||||
MediaItemKind::Image => {
|
||||
html! {
|
||||
(Meta("og:image", &self.url))
|
||||
(Optional(self.secure_url.as_ref(), |secure_url| Meta("og:image:secure_url", secure_url)))
|
||||
(Meta("og:image:type", &self.mime))
|
||||
(Optional(self.width.as_ref(), |width| Meta("og:image:width", &width.to_string())))
|
||||
(Optional(self.height.as_ref(), |height| Meta("og:image:height", &height.to_string())))
|
||||
(Meta("og:image:alt", &self.alt))
|
||||
}
|
||||
}
|
||||
MediaItemKind::Video => {
|
||||
html! {
|
||||
(Meta("og:video", &self.url))
|
||||
(Optional(self.secure_url.as_ref(), |secure_url| Meta("og:video:secure_url", secure_url)))
|
||||
(Meta("og:video:type", &self.mime))
|
||||
(Optional(self.width.as_ref(), |width| Meta("og:video:width", &width.to_string())))
|
||||
(Optional(self.height.as_ref(), |height| Meta("og:video:height", &height.to_string())))
|
||||
}
|
||||
}
|
||||
MediaItemKind::Audio => {
|
||||
html! {
|
||||
(Meta("og:audio", &self.url))
|
||||
(Optional(self.secure_url.as_ref(), |secure_url| Meta("og:audio:secure_url", secure_url)))
|
||||
(Meta("og:audio:type", &self.mime))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Song {
|
||||
pub duration: u32,
|
||||
pub album: String,
|
||||
pub album_disc: u32,
|
||||
pub album_track: u32,
|
||||
pub musician: Vec<String>,
|
||||
}
|
||||
|
||||
impl Song {
|
||||
pub fn new(duration: u32, album: &str, album_disc: u32, album_track: u32) -> Self {
|
||||
Self {
|
||||
duration,
|
||||
album: album.to_string(),
|
||||
album_disc,
|
||||
album_track,
|
||||
musician: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn musician(mut self, musician: &str) -> Self {
|
||||
self.musician.push(musician.to_string());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectType for Song {
|
||||
fn render(&self) -> PreEscaped<String> {
|
||||
html! {
|
||||
(Meta("og:type", "music.song"))
|
||||
(Meta("music:duration", &self.duration.to_string()))
|
||||
(Meta("music:album:disc", &self.album_disc.to_string()))
|
||||
(Meta("music:album:track", &self.album_track.to_string()))
|
||||
(array_meta(&self.musician, "music:musician"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Album {
|
||||
pub song: Vec<(String, u32, u32)>,
|
||||
pub musician: String,
|
||||
pub release_date: chrono::NaiveDate,
|
||||
}
|
||||
|
||||
impl Album {
|
||||
pub fn new(musician: &str, release_date: chrono::NaiveDate) -> Self {
|
||||
Self {
|
||||
song: Vec::new(),
|
||||
musician: musician.to_string(),
|
||||
release_date,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn song(mut self, song: &str, disc: u32, track: u32) -> Self {
|
||||
self.song.push((song.to_string(), disc, track));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectType for Album {
|
||||
fn render(&self) -> PreEscaped<String> {
|
||||
html! {
|
||||
@for song in &self.song {
|
||||
(Meta("music:song", &song.0))
|
||||
(Meta("music:song:disc", &song.1.to_string()))
|
||||
(Meta("music:song:track", &song.2.to_string()))
|
||||
}
|
||||
(Meta("music:musician", &self.musician))
|
||||
(Meta("music:release_date", &self.release_date.format("%Y-%m-%d").to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Playlist {
|
||||
pub song: Vec<(String, u32, u32)>,
|
||||
pub creator: String,
|
||||
}
|
||||
|
||||
impl Playlist {
|
||||
pub fn new(creator: &str) -> Self {
|
||||
Self {
|
||||
song: Vec::new(),
|
||||
creator: creator.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn song(mut self, song: &str, disc: u32, track: u32) -> Self {
|
||||
self.song.push((song.to_string(), disc, track));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectType for Playlist {
|
||||
fn render(&self) -> PreEscaped<String> {
|
||||
html! {
|
||||
@for song in &self.song {
|
||||
(Meta("music:song", &song.0))
|
||||
(Meta("music:song:disc", &song.1.to_string()))
|
||||
(Meta("music:song:track", &song.2.to_string()))
|
||||
}
|
||||
(Meta("music:creator", &self.creator))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RadioStation {
|
||||
pub creator: String,
|
||||
}
|
||||
|
||||
impl RadioStation {
|
||||
pub fn new(creator: &str) -> Self {
|
||||
Self {
|
||||
creator: creator.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectType for RadioStation {
|
||||
fn render(&self) -> PreEscaped<String> {
|
||||
html! {
|
||||
(Meta("music:creator", &self.creator))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Video {
|
||||
pub actor: Vec<(String, String)>,
|
||||
pub director: Vec<String>,
|
||||
pub writer: Vec<String>,
|
||||
pub duration: u32,
|
||||
pub release_date: chrono::NaiveDate,
|
||||
pub tag: Vec<String>,
|
||||
pub series: Option<String>,
|
||||
pub kind: VideoType,
|
||||
}
|
||||
|
||||
impl Video {
|
||||
pub fn actor(mut self, actor: &str, role: &str) -> Self {
|
||||
self.actor.push((actor.to_string(), role.to_string()));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn director(mut self, director: &str) -> Self {
|
||||
self.director.push(director.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn writer(mut self, writer: &str) -> Self {
|
||||
self.writer.push(writer.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tag(mut self, tag: &str) -> Self {
|
||||
self.tag.push(tag.to_string());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Video {
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Movie(duration: u32, release_date: chrono::NaiveDate) -> Self {
|
||||
Self {
|
||||
actor: Vec::new(),
|
||||
director: Vec::new(),
|
||||
writer: Vec::new(),
|
||||
duration,
|
||||
release_date,
|
||||
tag: Vec::new(),
|
||||
series: None,
|
||||
kind: VideoType::Movie,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Episode(duration: u32, release_date: chrono::NaiveDate) -> Self {
|
||||
Self {
|
||||
actor: Vec::new(),
|
||||
director: Vec::new(),
|
||||
writer: Vec::new(),
|
||||
duration,
|
||||
release_date,
|
||||
tag: Vec::new(),
|
||||
series: None,
|
||||
kind: VideoType::Episode,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn TV_Show(duration: u32, release_date: chrono::NaiveDate, series: &str) -> Self {
|
||||
Self {
|
||||
actor: Vec::new(),
|
||||
director: Vec::new(),
|
||||
writer: Vec::new(),
|
||||
duration,
|
||||
release_date,
|
||||
tag: Vec::new(),
|
||||
series: Some(series.to_string()),
|
||||
kind: VideoType::TV_Show,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn Other(duration: u32, release_date: chrono::NaiveDate) -> Self {
|
||||
Self {
|
||||
actor: Vec::new(),
|
||||
director: Vec::new(),
|
||||
writer: Vec::new(),
|
||||
duration,
|
||||
release_date,
|
||||
tag: Vec::new(),
|
||||
series: None,
|
||||
kind: VideoType::Other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectType for Video {
|
||||
fn render(&self) -> PreEscaped<String> {
|
||||
html! {
|
||||
(Meta("og:type", self.kind.kind()))
|
||||
@for actor in &self.actor {
|
||||
(Meta("video:actor", &actor.0))
|
||||
(Meta("video:actor:role", &actor.1))
|
||||
}
|
||||
(array_meta(&self.director, "video:director"))
|
||||
(array_meta(&self.writer, "video:writer"))
|
||||
(Meta("video:duration", &self.duration.to_string()))
|
||||
(Meta("video:release_date", &self.release_date.format("%Y-%m-%d").to_string()))
|
||||
(array_meta(&self.tag, "video:tag"))
|
||||
(Optional(self.series.as_ref(), |series| Meta("video.series", series)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum VideoType {
|
||||
Movie,
|
||||
Episode,
|
||||
#[allow(non_camel_case_types)]
|
||||
TV_Show,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl VideoType {
|
||||
pub fn kind(&self) -> &str {
|
||||
match self {
|
||||
VideoType::Movie => "video.movie",
|
||||
VideoType::Episode => "video.episode",
|
||||
VideoType::TV_Show => "video.tv_show",
|
||||
VideoType::Other => "video.other",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Article {
|
||||
pub published_time: chrono::NaiveDate,
|
||||
pub modified_time: chrono::NaiveDate,
|
||||
pub expiration_time: chrono::NaiveDate,
|
||||
pub author: Vec<String>,
|
||||
pub section: String,
|
||||
pub tag: Vec<String>,
|
||||
}
|
||||
|
||||
impl Article {
|
||||
pub fn new(
|
||||
published_time: chrono::NaiveDate,
|
||||
modified_time: chrono::NaiveDate,
|
||||
expiration_time: chrono::NaiveDate,
|
||||
section: &str,
|
||||
) -> Self {
|
||||
Self {
|
||||
published_time,
|
||||
modified_time,
|
||||
expiration_time,
|
||||
author: Vec::new(),
|
||||
section: section.to_string(),
|
||||
tag: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn author(mut self, author: &str) -> Self {
|
||||
self.author.push(author.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tag(mut self, tag: &str) -> Self {
|
||||
self.tag.push(tag.to_string());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectType for Article {
|
||||
fn render(&self) -> PreEscaped<String> {
|
||||
html! {
|
||||
(Meta("article:published_time", &self.published_time.format("%Y-%m-%d").to_string()))
|
||||
(Meta("article:modified_time", &self.modified_time.format("%Y-%m-%d").to_string()))
|
||||
(Meta("article:expiration_time", &self.expiration_time.format("%Y-%m-%d").to_string()))
|
||||
(array_meta(&self.author, "article:author"))
|
||||
(Meta("article:section", &self.section))
|
||||
(array_meta(&self.tag, "article:tag"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Book {
|
||||
pub author: Vec<String>,
|
||||
pub isbn: String,
|
||||
pub release_date: chrono::NaiveDate,
|
||||
pub tag: Vec<String>,
|
||||
}
|
||||
|
||||
impl Book {
|
||||
pub fn new(isbn: &str, release_date: chrono::NaiveDate) -> Self {
|
||||
Self {
|
||||
author: Vec::new(),
|
||||
isbn: isbn.to_string(),
|
||||
release_date,
|
||||
tag: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn author(mut self, author: &str) -> Self {
|
||||
self.author.push(author.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn tag(mut self, tag: &str) -> Self {
|
||||
self.tag.push(tag.to_string());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectType for Book {
|
||||
fn render(&self) -> PreEscaped<String> {
|
||||
html! {
|
||||
(array_meta(&self.author, "book:author"))
|
||||
(Meta("book:isbn", &self.isbn))
|
||||
(Meta("book:release_date", &self.release_date.format("%Y-%m-%d").to_string()))
|
||||
(array_meta(&self.tag, "book:tag"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Profile {
|
||||
first_name: String,
|
||||
last_name: String,
|
||||
username: String,
|
||||
gender: Gender,
|
||||
}
|
||||
|
||||
impl Profile {
|
||||
pub fn new(first_name: &str, last_name: &str, username: &str, gender: Gender) -> Self {
|
||||
Self {
|
||||
first_name: first_name.to_string(),
|
||||
last_name: last_name.to_string(),
|
||||
username: username.to_string(),
|
||||
gender,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Gender {
|
||||
Male,
|
||||
Female,
|
||||
None,
|
||||
}
|
||||
|
||||
impl ObjectType for Profile {
|
||||
fn render(&self) -> PreEscaped<String> {
|
||||
html! {
|
||||
(Meta("profile:first_name", &self.first_name))
|
||||
(Meta("profile:last_name", &self.last_name))
|
||||
(Meta("profile:username", &self.username))
|
||||
(Meta("profile:gender", match self.gender {
|
||||
Gender::Male => "male",
|
||||
Gender::Female => "female",
|
||||
Gender::None => "none",
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Website;
|
||||
|
||||
impl Website {
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectType for Website {
|
||||
fn render(&self) -> PreEscaped<String> {
|
||||
html! {
|
||||
(Meta("og:type", "website"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ObjectType {
|
||||
fn render(&self) -> PreEscaped<String>;
|
||||
}
|
|
@ -3,12 +3,13 @@ use std::sync::Arc;
|
|||
use maud::{PreEscaped, Render, html};
|
||||
|
||||
use crate::{
|
||||
ogp::Metadata,
|
||||
request::{RequestContext, StringResponse},
|
||||
ui::{
|
||||
UIWidget,
|
||||
color::{Gray, UIColor},
|
||||
htmx::{Event, HTMXAttributes, Selector, SwapStrategy},
|
||||
prelude::{Div, Link},
|
||||
prelude::{Div, Link, Optional},
|
||||
primitives::link::LinkWidget,
|
||||
},
|
||||
};
|
||||
|
@ -25,6 +26,7 @@ pub struct Shell {
|
|||
/// The HTML content for the static body portion.
|
||||
body_content: Arc<PreEscaped<String>>,
|
||||
ui: bool,
|
||||
metadata: Option<Metadata>,
|
||||
bottom_nav: Option<Arc<PreEscaped<String>>>,
|
||||
sidebar: Option<Arc<SidebarWidget>>,
|
||||
navbar: Option<Arc<NavBarWidget>>,
|
||||
|
@ -51,6 +53,7 @@ impl Shell {
|
|||
main_class: Arc::new(body_class.extended_class().join(" ")),
|
||||
body_content: Arc::new(body_content.render()),
|
||||
ui: false,
|
||||
metadata: None,
|
||||
bottom_nav: None,
|
||||
sidebar: None,
|
||||
navbar: None,
|
||||
|
@ -69,6 +72,11 @@ impl Shell {
|
|||
self.clone()
|
||||
}
|
||||
|
||||
pub fn metadata(mut self, metadata: Metadata) -> Self {
|
||||
self.metadata = Some(metadata);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_navbar(mut self, navbar: NavBarWidget) -> Self {
|
||||
self.navbar = Some(Arc::new(navbar));
|
||||
self
|
||||
|
@ -123,6 +131,7 @@ impl Shell {
|
|||
meta name="viewport" content="width=device-width, initial-scale=1.0";
|
||||
};
|
||||
(self.head)
|
||||
(Optional(self.metadata.as_ref(), |meta| meta.render()))
|
||||
};
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue