use super::css::StyleSheet; use super::html::{ replace_placeholders, HtmlDoc, HtmlElement, CSS_LINK_FRAGMENT, FAVICON_LINK_FRAGMENT, IMAGE_LINKS_FRAGMENT, SCRIPT_FRAGMENT, }; use serde::{Deserialize, Serialize}; use std::collections::HashMap; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Page { pub template_name: String, pub body: PageBody, pub metadata: PageMetadata, #[serde(default = "Vec::new")] pub sub_pages: Vec<Page>, #[serde(default = "HtmlDoc::new")] pub html: HtmlDoc, pub template: Option<PageTemplate>, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct PageTemplate { pub layout: StyleSheet, pub name: String, pub contents: Vec<HtmlElement>, } impl PageTemplate { pub fn get_page_body_placeholder<'a>( elements: &'a mut Vec<HtmlElement>, ) -> Result<&'a mut HtmlElement, ()> { for el in elements.iter_mut() { if el.attrs.get("id").unwrap_or(&String::new()).eq("page-body") { return Ok(el); } else if let Some(children) = &mut el.contents { if let Ok(found) = Self::get_page_body_placeholder(children) { return Ok(found); } } } return Err(()); } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct PageBody(Vec<HtmlElement>); impl std::fmt::Display for PageBody { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "{}", &self .0 .iter() .map(|i| i.to_string()) .collect::<Vec<String>>() .join("") ) } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct PageMetadata { pub title: String, pub lang: String, pub description: String, pub url_slug: String, #[serde(default = "CSSLinks::new")] pub css: CSSLinks, #[serde(default = "JSLinks::new")] pub js: JSLinks, #[serde(default = "FaviconLink::new")] pub favicon: FaviconLink, #[serde(default = "String::new")] pub author: String, #[serde(default = "ImageLinks::new")] pub image: ImageLinks, } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct FaviconLink(String); impl FaviconLink { pub fn new() -> Self { FaviconLink(String::from("/static/default/favicon.ico")) } } impl std::fmt::Display for FaviconLink { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "{}", replace_placeholders(FAVICON_LINK_FRAGMENT, { let mut map = HashMap::new(); map.insert("url".to_owned(), self.0.to_owned()); map }) ) } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct CSSLinks(Vec<String>); impl CSSLinks { pub fn new() -> Self { CSSLinks(vec!["/static/default/style.css".to_owned()]) } } impl std::fmt::Display for CSSLinks { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "{}", &self .0 .iter() .map(|url| replace_placeholders(CSS_LINK_FRAGMENT, { let mut map = HashMap::new(); map.insert("url".to_string(), url.to_owned()); map })) .collect::<Vec<String>>() .join("\n") ) } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct JSLinks(Vec<String>); impl JSLinks { pub fn new() -> Self { JSLinks(vec!["/static/default/script.js".to_owned()]) } } impl std::fmt::Display for JSLinks { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "{}", &self .0 .iter() .map(|url| replace_placeholders(SCRIPT_FRAGMENT, { let mut map = HashMap::new(); map.insert("url".to_string(), url.to_owned()); map })) .collect::<Vec<String>>() .join("\n") ) } } #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ImageLinks(Vec<String>); impl ImageLinks { pub fn new() -> Self { ImageLinks(vec![]) } } impl std::fmt::Display for ImageLinks { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "{}", &self .0 .iter() .map(|url| replace_placeholders(IMAGE_LINKS_FRAGMENT, { let mut map = HashMap::new(); map.insert("url".to_string(), url.to_owned()); map })) .collect::<Vec<String>>() .join("\n") ) } } impl Page { pub fn build_html(&mut self) { self.html = HtmlDoc::from_page(self); } pub fn build_with_template(&mut self, template: PageTemplate) { let mut complete_body = template.clone(); self.template = Some(template); PageTemplate::get_page_body_placeholder(&mut complete_body.contents) .expect("Couldn't find page body container in template") .set_contents(self.body.0.clone()); self.body.0 = complete_body.contents; } pub fn to_map(&self) -> HashMap<String, String> { let mut map = HashMap::new(); map.insert("title".to_string(), self.metadata.title.to_owned()); map.insert("lang".to_string(), self.metadata.lang.to_owned()); map.insert( "description".to_string(), self.metadata.description.to_owned(), ); map.insert("slug".to_string(), self.metadata.url_slug.to_owned()); map.insert("body".to_string(), self.body.to_string()); map.insert("css".to_string(), self.metadata.css.to_string()); map.insert("js".to_string(), self.metadata.js.to_string()); map.insert("author".to_string(), self.metadata.author.to_string()); map.insert("image".to_string(), self.metadata.image.to_string()); map } } #[cfg(test)] mod test_pages { use super::*; use crate::website::html::{HtmlAttributes, HtmlElement}; fn test_page_metadata() -> PageMetadata { PageMetadata { title: String::from("Test"), lang: String::from("en"), description: String::from("test descr"), url_slug: String::from("test-page"), css: CSSLinks(vec![ "/static/source_code/mystyle.css".to_string(), "/static/source_code/mystyle2.css".to_string(), ]), js: JSLinks(vec![ "/static/source_code/myscript.js".to_string(), "/static/source_code/myscript2.js".to_string(), ]), favicon: FaviconLink(String::from("/static/images/testicon.ico")), author: String::from("test author"), image: ImageLinks(vec![ "/static/images/testimage.png".to_string(), "/static/images/testimage2.png".to_string(), ]), } } fn test_page_template() -> PageTemplate { PageTemplate { layout: StyleSheet(HashMap::new()), name: String::from("test template"), contents: vec![HtmlElement { tag: String::from("div"), attrs: { let mut attrs = HtmlAttributes::new(); attrs .0 .insert(String::from("id"), String::from("test-template")); attrs }, contents: Some(vec![ HtmlElement { tag: String::from("nav"), attrs: HtmlAttributes::new(), contents: None, text: Some(String::from("NAV")), }, HtmlElement { tag: String::from("div"), attrs: { let mut attrs = HtmlAttributes::new(); attrs .0 .insert(String::from("id"), String::from("page-body")); attrs }, contents: None, text: None, }, HtmlElement { tag: String::from("footer"), attrs: HtmlAttributes::new(), contents: None, text: Some(String::from("FOOTER")), }, ]), text: None, }], } } fn test_page() -> Page { Page { template_name: String::from("test template"), body: PageBody(vec![HtmlElement { tag: "span".to_string(), text: Some("TEST".to_string()), contents: None, attrs: HtmlAttributes::new(), }]), metadata: test_page_metadata(), sub_pages: Vec::new(), html: HtmlDoc::new(), template: None, } } #[test] fn page_body_to_string() { let body = PageBody(vec![ HtmlElement { tag: "span".to_string(), text: Some("Hello ".to_string()), contents: None, attrs: HtmlAttributes::new(), }, HtmlElement { tag: "span".to_string(), text: Some("World!".to_string()), contents: None, attrs: HtmlAttributes::new(), }, ]); assert_eq!(body.to_string(), "<span>Hello </span><span>World!</span>"); } #[test] fn favicon_link_to_string() { let pmd = test_page_metadata(); assert_eq!( pmd.favicon.to_string(), "<link rel='icon' type='image/*' href='/static/images/testicon.ico'/>" ) } #[test] fn css_links_to_string() { let pmd = test_page_metadata(); assert_eq!( pmd.css.to_string(), "<link rel='stylesheet' href='/static/source_code/mystyle.css'> <link rel='stylesheet' href='/static/source_code/mystyle2.css'>" ) } #[test] fn js_links_to_string() { let pmd = test_page_metadata(); assert_eq!( pmd.js.to_string(), "<script src='/static/source_code/myscript.js'></script> <script src='/static/source_code/myscript2.js'></script>" ) } #[test] fn images_links_to_string() { let pmd = test_page_metadata(); assert_eq!( pmd.image.to_string(), "<meta name='image' content='/static/images/testimage.png'/> <meta property='og:image' content='/static/images/testimage.png'/> <meta property='twitter:image' content='/static/images/testimage.png'/> <meta name='image' content='/static/images/testimage2.png'/> <meta property='og:image' content='/static/images/testimage2.png'/> <meta property='twitter:image' content='/static/images/testimage2.png'/>" ) } #[test] fn build_body_with_template() { let mut page = test_page(); page.build_with_template(test_page_template()); assert_eq!( page.body.to_string(), "<div id=\"test-template\"><nav>NAV</nav><div id=\"page-body\"><span>TEST</span></div><footer>FOOTER</footer></div>" ) } }