Pour tout problème contactez-nous par mail : support@froggit.fr | La FAQ :grey_question: | Rejoignez-nous sur le Chat :speech_balloon:

Skip to content
Snippets Groups Projects
page.rs 14.5 KiB
Newer Older
  • Learn to ignore specific revisions
  • peterrabbit's avatar
    peterrabbit committed
    use super::css::StyleSheet;
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
    use super::html::{
        replace_placeholders, HtmlDoc, HtmlElement, CSS_LINK_FRAGMENT, FAVICON_LINK_FRAGMENT,
        IMAGE_LINKS_FRAGMENT, SCRIPT_FRAGMENT,
    };
    
    use crate::StaticFilesManager;
    
    use serde::{
        ser::{SerializeStruct, Serializer},
        Deserialize, Serialize,
    };
    
    peterrabbit's avatar
    peterrabbit committed
    use std::collections::HashMap;
    
    use std::path::PathBuf;
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
    #[derive(Deserialize, Serialize, Debug, Clone)]
    pub struct PageId(usize);
    impl Default for PageId {
        fn default() -> Self {
            PageId(0)
        }
    }
    
    
    #[derive(Debug, Deserialize, Clone)]
    
    peterrabbit's avatar
    peterrabbit committed
    pub struct Page {
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
        #[serde(default)]
        id: PageId,
    
    peterrabbit's avatar
    peterrabbit committed
        pub template_name: String,
        pub body: PageBody,
    
        pub full_body: Option<PageBody>,
    
    peterrabbit's avatar
    peterrabbit committed
        pub metadata: PageMetadata,
    
    peterrabbit's avatar
    peterrabbit committed
        #[serde(default = "Vec::new")]
    
    peterrabbit's avatar
    peterrabbit committed
        pub sub_pages: Vec<Page>,
    
        #[serde(default = "HtmlDoc::new")]
    
    peterrabbit's avatar
    peterrabbit committed
        pub html: HtmlDoc,
    
    peterrabbit's avatar
    peterrabbit committed
        pub template: Option<PageTemplate>,
    
    impl Serialize for Page {
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            let mut state = serializer.serialize_struct("Page", 5)?;
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
            state.serialize_field("id", &self.id.0)?;
    
            state.serialize_field("template_name", &self.template_name)?;
            state.serialize_field("body", &self.body)?;
            state.serialize_field("metadata", &self.metadata)?;
            state.serialize_field("sub_pages", &self.sub_pages)?;
    
            state.end()
        }
    }
    
    
    peterrabbit's avatar
    peterrabbit committed
    #[derive(Debug, Serialize, Deserialize, Clone)]
    
    peterrabbit's avatar
    peterrabbit committed
    pub struct PageTemplate {
    
    peterrabbit's avatar
    peterrabbit committed
        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(());
        }
    }
    
    
    peterrabbit's avatar
    peterrabbit committed
    #[derive(Debug, Serialize, Deserialize, Clone)]
    
    peterrabbit's avatar
    peterrabbit committed
    pub struct PageBody(Vec<HtmlElement>);
    
    peterrabbit's avatar
    peterrabbit committed
    
    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,
    
    peterrabbit's avatar
    peterrabbit committed
        #[serde(default = "CSSLinks::new")]
        pub css: CSSLinks,
        #[serde(default = "JSLinks::new")]
        pub js: JSLinks,
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
        #[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("/assets/default/favicon.ico"))
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
        }
    }
    
    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
                })
            )
        }
    
    peterrabbit's avatar
    peterrabbit committed
    }
    
    #[derive(Debug, Serialize, Deserialize, Clone)]
    pub struct CSSLinks(Vec<String>);
    
    impl CSSLinks {
        pub fn new() -> Self {
    
            CSSLinks(vec!["/assets/default/style.css".to_owned()])
    
    peterrabbit's avatar
    peterrabbit committed
        }
    }
    
    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>>()
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
                    .join("\n")
    
    peterrabbit's avatar
    peterrabbit committed
            )
        }
    }
    
    #[derive(Debug, Serialize, Deserialize, Clone)]
    pub struct JSLinks(Vec<String>);
    
    impl JSLinks {
        pub fn new() -> Self {
    
            JSLinks(vec!["/assets/default/script.js".to_owned()])
    
    peterrabbit's avatar
    peterrabbit committed
        }
    }
    
    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>>()
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
                    .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")
    
    peterrabbit's avatar
    peterrabbit committed
            )
        }
    
    peterrabbit's avatar
    peterrabbit committed
    }
    
    impl Page {
    
        pub fn build(
            &mut self,
            templates: &Vec<PageTemplate>,
    
            parent_scope: PathBuf,
    
            static_files_manager: &StaticFilesManager,
    
        ) -> Result<(), String> {
    
            self.set_template(self.template(templates).clone());
            self.build_full_body();
    
            self.build_html();
    
            let url = self.url(parent_scope);
    
            if let Err(err) = self.write_to_static_file(&url, static_files_manager) {
                return Err(format!(
                    "Error writing html static file for page {} - {}",
                    self.metadata.title, err
                ));
            }
    
    
            for p in self.sub_pages.iter_mut() {
    
                if let Err(err) = p.build(templates, url.clone(), static_files_manager) {
                    return Err(format!(
                        "Error building page {} - {}",
                        p.metadata.title, err
                    ));
                }
    
        pub fn set_template(&mut self, template: PageTemplate) {
            self.template = Some(template);
        }
    
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
        pub fn get_id(&self) -> usize {
            self.id.0
        }
    
        pub fn set_id(&mut self, id: usize) {
            self.id.0 = id
        }
    
    
        fn build_html(&mut self) {
    
    peterrabbit's avatar
    peterrabbit committed
            self.html = HtmlDoc::from_page(self);
    
        fn build_full_body(&mut self) {
            let mut full_body = self.template.clone().expect("Page template is not set");
    
            // insert the page body elements in the template page-body container.
            PageTemplate::get_page_body_placeholder(&mut full_body.contents)
    
                .expect("Couldn't find page body container in template")
    
    peterrabbit's avatar
    peterrabbit committed
                .set_contents(self.body.0.clone());
    
            self.full_body = Some(PageBody(full_body.contents));
    
        fn url(&self, parent_scope: PathBuf) -> PathBuf {
            parent_scope.join(&self.metadata.url_slug)
        }
    
        fn template<'a>(&self, templates: &'a Vec<PageTemplate>) -> &'a PageTemplate {
            templates
                .iter()
                .find(|t| t.name == self.template_name)
                .expect("Page template not found")
        }
        fn write_to_static_file(
            &self,
            url: &PathBuf,
            static_files_manager: &StaticFilesManager,
        ) -> std::io::Result<()> {
            static_files_manager.write_html_page(&url, self)
        }
    
    
    peterrabbit's avatar
    peterrabbit committed
        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.full_body
                    .as_ref()
                    .expect("Page full body is not built")
                    .to_string(),
            );
    
    peterrabbit's avatar
    peterrabbit committed
            map.insert("css".to_string(), self.metadata.css.to_string());
            map.insert("js".to_string(), self.metadata.js.to_string());
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
            map.insert("author".to_string(), self.metadata.author.to_string());
    
            map.insert("favicon".to_string(), self.metadata.favicon.to_string());
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
            map.insert("image".to_string(), self.metadata.image.to_string());
    
    peterrabbit's avatar
    peterrabbit committed
    
            map
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
    
        pub fn add_sub_page(&mut self, page: Page) {
            self.sub_pages.push(page);
        }
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
    
    #[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![
    
                    "/assets/source_code/mystyle.css".to_string(),
                    "/assets/source_code/mystyle2.css".to_string(),
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
                ]),
                js: JSLinks(vec![
    
                    "/assets/source_code/myscript.js".to_string(),
                    "/assets/source_code/myscript2.js".to_string(),
    
                favicon: FaviconLink(String::from("/assets/images/testicon.ico")),
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
                author: String::from("test author"),
                image: ImageLinks(vec![
    
                    "/assets/images/testimage.png".to_string(),
                    "/assets/images/testimage2.png".to_string(),
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
                ]),
            }
        }
    
        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,
                }],
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
            }
        }
    
        fn test_page() -> Page {
            Page {
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
                id: PageId(1),
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
                template_name: String::from("test template"),
                body: PageBody(vec![HtmlElement {
                    tag: "span".to_string(),
                    text: Some("TEST".to_string()),
                    contents: None,
                    attrs: HtmlAttributes::new(),
                }]),
    
                full_body: None,
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
                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='/assets/images/testicon.ico'/>"
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
            )
        }
    
        #[test]
        fn css_links_to_string() {
            let pmd = test_page_metadata();
            assert_eq!(
                pmd.css.to_string(),
    
                "<link rel='stylesheet' href='/assets/source_code/mystyle.css'>
    <link rel='stylesheet' href='/assets/source_code/mystyle2.css'>"
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
            )
        }
    
        #[test]
        fn js_links_to_string() {
            let pmd = test_page_metadata();
            assert_eq!(
                pmd.js.to_string(),
    
                "<script src='/assets/source_code/myscript.js'></script>
    <script src='/assets/source_code/myscript2.js'></script>"
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
            )
        }
    
        #[test]
        fn images_links_to_string() {
            let pmd = test_page_metadata();
            assert_eq!(
                pmd.image.to_string(),
    
                "<meta name='image' content='/assets/images/testimage.png'/>
    <meta property='og:image' content='/assets/images/testimage.png'/>
    <meta property='twitter:image' content='/assets/images/testimage.png'/>
    <meta name='image' content='/assets/images/testimage2.png'/>
    <meta property='og:image' content='/assets/images/testimage2.png'/>
    <meta property='twitter:image' content='/assets/images/testimage2.png'/>"
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
            )
        }
    
        #[test]
        fn build_body_with_template() {
            let mut page = test_page();
    
            page.set_template(test_page_template());
            page.build_full_body();
    
    Pierre Jarriges's avatar
    Pierre Jarriges committed
            assert_eq!(
    
                page.full_body.unwrap().to_string(),
    
                "<div id=\"test-template\"><nav>NAV</nav><div id=\"page-body\"><span>TEST</span></div><footer>FOOTER</footer></div>"