diff --git a/example.json b/example.json index 1c11d168fe1adfb637ce1b99941ed46b12b90321..933668a1d42c8ed5650b3a38ec7d7c556201d512 100644 --- a/example.json +++ b/example.json @@ -31,23 +31,6 @@ ], "sub_pages": [] }, - "assets_index": { - "images": [ - "/static/images/toto.jpg" - ], - "sounds": [ - "/static/sounds/toto.mp3" - ], - "video": [ - "/static/video/toto.mp4" - ], - "docs": [ - "/static/docs/toto.xcf" - ], - "source_code": [ - "/static/source_code/toto.js" - ] - }, "templates": [ { "name": "Pijar Custom template", @@ -79,5 +62,22 @@ } ] } - ] + ], + "assets_index": { + "images": [ + "/static/images/toto.jpg" + ], + "sounds": [ + "/static/sounds/toto.mp3" + ], + "video": [ + "/static/video/toto.mp4" + ], + "docs": [ + "/static/docs/toto.xcf" + ], + "source_code": [ + "/static/source_code/toto.js" + ] + } } \ No newline at end of file diff --git a/src/static_files.rs b/src/static_files.rs index 7f33f484afd3cbf5ae06747aa4787278232ca2c9..dba90069c7f68e3726292dfc38b40d16baeed592 100644 --- a/src/static_files.rs +++ b/src/static_files.rs @@ -24,7 +24,7 @@ impl StaticFilesManager { if !static_dir.exists() { match std::fs::create_dir_all(&static_dir) { Ok(_) => { - if let Err(err) = Self::copy_default(&static_dir) { + if let Err(err) = Self::copy_default_files(&static_dir) { return Err(format!("{}", err)); } } @@ -35,19 +35,15 @@ impl StaticFilesManager { Ok(static_dir) } - fn copy_default(static_dir: &PathBuf) -> Result<(), String> { + fn copy_default_files(static_dir: &PathBuf) -> Result<(), String> { let local_default_static = std::env::current_dir().unwrap().join("default_static"); let default_static = static_dir.join("default"); match std::fs::create_dir_all(&default_static) { Err(err) => Err(format!("{}", err)), Ok(_) => { - let mut copy_default_options = fs_extra::dir::CopyOptions::new(); - copy_default_options.content_only = true; - match fs_extra::dir::copy( - local_default_static, - default_static, - ©_default_options, - ) { + let mut cpy_options = fs_extra::dir::CopyOptions::new(); + cpy_options.content_only = true; + match fs_extra::dir::copy(local_default_static, default_static, &cpy_options) { Err(err) => Err(format!("{}", err)), Ok(_) => Ok(()), } diff --git a/src/testing.rs b/src/testing.rs index c9b4bdb8ef46e48d326e9b43f1eb74326a3502a5..c24e7aba225b6c559802ad886d605ebdd66bd0c5 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -6,7 +6,7 @@ pub const TEST_JSON_WEBSITE: &'static str = " \"metadata\": { \"title\": \"TEST\", \"description\": \"TEST DESCRIPTION\", - \"image\": \"https://test/static/images/test.png\", + \"image\": [\"https://test/static/images/test.png\"], \"css\": [], \"lang\":\"en\", \"js\": [], @@ -24,7 +24,7 @@ pub const TEST_JSON_WEBSITE: &'static str = " \"metadata\": { \"title\": \"TEST SUBPAGE\", \"description\": \"TEST DESCRIPTION SUBPAGE\", - \"image\": \"https://test/static/images/test.png\", + \"image\": [\"https://test/static/images/test.png\"], \"css\": [], \"lang\":\"en\", \"js\": [], @@ -40,9 +40,9 @@ pub const TEST_JSON_WEBSITE: &'static str = " { \"template_name\": \"TEST TEMPLATE\", \"metadata\": { - \"title\": \"TEST NESTTED\", + \"title\": \"TEST NESTED\", \"description\": \"TEST DESCRIPTION NESTED\", - \"image\": \"https://test/static/images/test.png\", + \"image\": [\"https://test/static/images/test.png\"], \"css\": [], \"js\": [], \"url_slug\": \"nested\", diff --git a/src/website/html.rs b/src/website/html.rs index 0563966178743f6c60ebbd2ea8699a9e36d2bca7..383945ee61cc21124e833fd1b5057b99e37543dd 100644 --- a/src/website/html.rs +++ b/src/website/html.rs @@ -5,14 +5,23 @@ use std::collections::HashMap; pub const CSS_LINK_FRAGMENT: &'static str = "<link rel='stylesheet' href='{url}'>"; pub const SCRIPT_FRAGMENT: &'static str = "<script src='{url}'></script>"; +pub const FAVICON_LINK_FRAGMENT: &'static str = "<link rel='icon' type='image/*' href='{url}'/>"; +pub const IMAGE_LINKS_FRAGMENT: &'static str = "<meta name='image' content='{url}'/> +<meta property='og:image' content='{url}'/> +<meta property='twitter:image' content='{url}'/>"; const HTML_DOC_TEMPLATE: &'static str = " -<html lang='{lang}'> +<html lang='{lang}' prefix='og: https://ogp.me/ns#'> <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'> <meta name='description' content='{description}'> + <meta name='author' content='{author}'> + + <meta property='og:title' content='{title}'/> + <meta property='og:description' content='{description}'/> + {image} <title>{title}</title> {css} </head> @@ -20,9 +29,7 @@ const HTML_DOC_TEMPLATE: &'static str = " <body> {body} </body> - {js} - </html> "; diff --git a/src/website/page.rs b/src/website/page.rs index c4b33a730a5365d8d349e3ba792cef75175b5d6d..54771e14073447bd4deaa52f4fa64820c91ae20b 100644 --- a/src/website/page.rs +++ b/src/website/page.rs @@ -1,5 +1,8 @@ use super::css::StyleSheet; -use super::html::{replace_placeholders, HtmlDoc, HtmlElement, CSS_LINK_FRAGMENT, SCRIPT_FRAGMENT}; +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; @@ -50,6 +53,35 @@ pub struct PageMetadata { 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)] @@ -75,7 +107,7 @@ impl std::fmt::Display for CSSLinks { map })) .collect::<Vec<String>>() - .join("") + .join("\n") ) } } @@ -103,7 +135,35 @@ impl std::fmt::Display for JSLinks { map })) .collect::<Vec<String>>() - .join("") + .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") ) } } @@ -113,20 +173,19 @@ impl Page { self.html = HtmlDoc::from_page(self); } - pub fn build_body_template(&mut self, template: PageTemplate) { + pub fn build_with_template(&mut self, template: PageTemplate) { let mut complete_body = template.clone(); self.template = Some(template); let err = "Couldn't find page-body placeholder"; // Insert page body inside the template page-body container. - // Then replace the page body by the complete template complete_body .contents .iter_mut() .find(|el| el.attrs.get("id").unwrap_or(&String::new()).eq("page-body")) .expect(err) .set_contents(self.body.0.clone()); - + // Then replace the page body by the complete template self.body.0 = complete_body.contents; } @@ -142,7 +201,160 @@ impl Page { 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("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")), + }, + ], + } + } + + 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(), + "<nav>NAV</nav><div id=\"page-body\"><span>TEST</span></div><footer>FOOTER</footer>" + ) + } +} diff --git a/src/website/website.rs b/src/website/website.rs index 20274b3ba01d7a2175a57fe8fcecc29c64243abe..dca24e6310168e94f215158894f5f7187a54b9ae 100644 --- a/src/website/website.rs +++ b/src/website/website.rs @@ -39,7 +39,7 @@ impl WebSite { pub fn from_json(json: &str) -> Self { let mut obj: WebSite = serde_json::from_str(json).unwrap(); - obj.root_page.build_body_template( + obj.root_page.build_with_template( obj.templates .iter() .find(|t| t.name == obj.root_page.template_name) @@ -50,7 +50,7 @@ impl WebSite { obj.root_page.build_html(); for p in obj.root_page.sub_pages.iter_mut() { - p.build_body_template( + p.build_with_template( obj.templates .iter() .find(|t| t.name == p.template_name) @@ -107,17 +107,14 @@ mod test_website { let website = WebSite::from_json(TEST_JSON_WEBSITE); let root_page = website.get_page_by_url(&PathBuf::from("/")); assert!(root_page.is_some()); - let root_page = root_page.unwrap(); - assert_eq!(format!("{}", root_page.body), "<h1>testing</h1>"); + assert_eq!(root_page.unwrap().metadata.title, "TEST"); let sub_page = website.get_page_by_url(&PathBuf::from("subpage")); assert!(sub_page.is_some()); - let sub_page = sub_page.unwrap(); - assert_eq!(format!("{}", sub_page.body), "<h1>testing subpage</h1>"); + assert_eq!(sub_page.unwrap().metadata.title, "TEST SUBPAGE"); let nested_page = website.get_page_by_url(&PathBuf::from("subpage/nested")); assert!(nested_page.is_some()); - let nested_page = nested_page.unwrap(); - assert_eq!(format!("{}", nested_page.body), "<h1>testing nested</h1>"); + assert_eq!(nested_page.unwrap().metadata.title, "TEST NESTED"); } } diff --git a/templates/new_website.json b/templates/new_website.json index c13fb2d710b44dbfcbcfe476afc4abaefd471fda..417fea9c836e936fa7ae23e9669aa8f54f514f25 100644 --- a/templates/new_website.json +++ b/templates/new_website.json @@ -4,9 +4,11 @@ "metadata": { "title": "New Website", "description": "A new website", - "image": "", "url_slug": "", - "lang": "en" + "lang": "en", + "image": [ + "https://example.com/image.png" + ] }, "body": [ {