diff --git a/Cargo.lock b/Cargo.lock index d315b0e..f72055e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -560,9 +560,9 @@ dependencies = [ [[package]] name = "cached" -version = "0.47.0" +version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b0116662497bc24e4b177c90eaf8870e39e2714c3fcfa296327a93f593fc21" +checksum = "355face540df58778b96814c48abb3c2ed67c4878a8087ab1819c1fedeec505f" dependencies = [ "ahash 0.8.3", "async-trait", @@ -578,9 +578,9 @@ dependencies = [ [[package]] name = "cached_proc_macro" -version = "0.18.1" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c878c71c2821aa2058722038a59a67583a4240524687c6028571c9b395ded61f" +checksum = "9d52f526f7cbc875b296856ca8c964a9f6290556922c303a8a3883e3c676e6a1" dependencies = [ "darling", "proc-macro2", @@ -590,9 +590,9 @@ dependencies = [ [[package]] name = "cached_proc_macro_types" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663" +checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" [[package]] name = "cc" diff --git a/Cargo.toml b/Cargo.toml index b958db6..14c77eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ license = "AGPL-3.0-or-later" [dependencies] actix-web = { version = "4.4", default-features = false, features = ["macros", "compress-brotli"] } actix-files = "0.6" -cached = { version = "0.47", features = ["async"] } +cached = { version = "0.48", features = ["async"] } ramhorns = "0.14" toml = "0.8" serde = { version = "1.0", features = ["derive"] } diff --git a/Documentation.md b/Documentation.md index 4c8b154..67844ab 100644 --- a/Documentation.md +++ b/Documentation.md @@ -10,6 +10,7 @@ - [Global configuration](#global-configuration) - [Link shortener for contacts](#link-shortener-for-contacts) - [Add content](#add-content) + - [Index](#index) - [Blog](#blog) - [Projects](#projects) - [Contacts](#contacts) @@ -139,9 +140,27 @@ option: value Markdown file ``` +## Index + +Markdown file is stored in `/app/data/index.md` + +``` +--- +name: Option +pronouns: Option +avatar: Option +avatar_caption: Option +--- + +Index content +``` + +- If no `name`, the `fullname` used in the configuration will be used +- `avatar` is the link of the avatar + ## Blog -Markdown files are stored in `/app/data/blog/` +Markdown files are stored in `/app/data/blog/posts/` ``` --- @@ -158,11 +177,15 @@ Post content - If no `title`, the filename will be used - `date` format is `day-month-year` - `publish` is default to false. When false, posts are hidden from index - but accessible, see #30 + but accessible, see [#30](https://git.mylloon.fr/Anri/mylloon.fr/issues/30) + +### About + +The file is stored at `/app/data/blog/about.md`. ## Projects -Markdown files are stored in `/app/data/projects/` +Markdown files are stored in `/app/data/projects/apps/` ``` --- @@ -177,7 +200,14 @@ Project description - If no `link` : the div won't be clickable and will be reported as is to the user (no corner-arrow) -- Note that only a handful of [`language`s are supported](./static/css/languages.css). +- Note that only a handful of [`language`s are supported](./static/css/languages.css) + +You can also put apps in an "Archived" category, in this case, store markdown +files in `archive` subdirectory of `apps`. + +### About + +The file is stored at `/app/data/projects/about.md`. ## Contacts @@ -206,6 +236,18 @@ Custom project description - `description` will be rendered as HTML "title" (text will appear when cursor is hover the link) +Also, contacts are categorized, here is the list of the available categories: + +- `socials` +- `forges` +- `others` + +For example, `socials` contact files are stored in `/app/data/contacts/socials/`. + +### About + +The file is stored at `/app/data/contacts/about.md`. + ## Courses Markdown files are stored in `/app/data/cours/` diff --git a/src/main.rs b/src/main.rs index 289fa22..2fc17c8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,7 +47,7 @@ async fn main() -> Result<()> { .service(agreements::security) .service(agreements::humans) .service(agreements::robots) - .service(agreements::sitemap) + .service(agreements::webmanifest) .service(blog::index) .service(blog::rss) .service(blog::page) diff --git a/src/routes/agreements.rs b/src/routes/agreements.rs index 3589c37..95acbee 100644 --- a/src/routes/agreements.rs +++ b/src/routes/agreements.rs @@ -71,8 +71,29 @@ fn build_robotstxt() -> String { "User-agent: * Allow: /".into() } -#[get("/sitemap.xml")] -async fn sitemap() -> impl Responder { - // TODO - actix_web::web::Redirect::to("/") +#[get("/app.webmanifest")] +async fn webmanifest(config: web::Data) -> impl Responder { + HttpResponse::Ok() + .content_type(ContentType("application/manifest+json".parse().unwrap())) + .body(build_webmanifest(config.get_ref().to_owned())) +} + +#[derive(Content, Debug)] +struct WebManifestTemplate { + name: String, + description: String, + url: String, +} + +#[once(time = 60)] +fn build_webmanifest(config: Config) -> String { + config.tmpl.render( + "app.webmanifest", + WebManifestTemplate { + name: config.fc.clone().app_name.unwrap(), + description: "Easy WebPage generator".to_owned(), + url: get_url(config.fc), + }, + Infos::default(), + ) } diff --git a/src/routes/blog.rs b/src/routes/blog.rs index c40a36d..aec3019 100644 --- a/src/routes/blog.rs +++ b/src/routes/blog.rs @@ -7,7 +7,7 @@ use ::rss::{ extension::atom::{AtomExtension, Link}, Category, Channel, Guid, Image, Item, }; -use actix_web::{get, web, HttpResponse, Responder}; +use actix_web::{get, http::header::ContentType, web, HttpResponse, Responder}; use cached::proc_macro::once; use chrono::{DateTime, Datelike, Local, NaiveDateTime, Utc}; use chrono_tz::Europe; @@ -27,6 +27,8 @@ use crate::{ }; const MIME_TYPE_RSS: &str = "application/rss+xml"; +const BLOG_DIR: &str = "blog"; +const POST_DIR: &str = "posts"; #[get("/blog")] async fn index(config: web::Data) -> impl Responder { @@ -36,13 +38,19 @@ async fn index(config: web::Data) -> impl Responder { #[derive(Content, Debug)] struct BlogIndexTemplate { navbar: NavBar, + about: Option, posts: Vec, no_posts: bool, } #[once(time = 60)] fn build_index(config: Config) -> String { - let mut posts = get_posts(format!("{}/blog", config.locations.data_dir)); + let blog_dir = format!("{}/{}", config.locations.data_dir, BLOG_DIR); + let mut posts = get_posts(format!("{}/{}", blog_dir, POST_DIR)); + + // Get about + let about: Option = + read_file(&format!("{}/about.md", blog_dir), TypeFileMetadata::Generic); // Sort from newest to oldest posts.sort_by_cached_key(|p| (p.date.year, p.date.month, p.date.day)); @@ -55,6 +63,7 @@ fn build_index(config: Config) -> String { blog: true, ..NavBar::default() }, + about, no_posts: posts.is_empty(), posts, }, @@ -82,7 +91,7 @@ struct Post { impl Post { // Fetch the file content fn fetch_content(&mut self, data_dir: &str) { - let blog_dir = format!("{}/blog", data_dir); + let blog_dir = format!("{}/{}/{}", data_dir, BLOG_DIR, POST_DIR); let ext = ".md"; if let Some(file) = read_file( @@ -217,7 +226,7 @@ fn get_post( name: String, data_dir: String, ) -> (Infos, String) { - let blog_dir = format!("{}/blog", data_dir); + let blog_dir = format!("{}/{}/{}", data_dir, BLOG_DIR, POST_DIR); let ext = ".md"; *post = read_file( @@ -272,13 +281,16 @@ fn get_post( #[get("/blog/rss")] async fn rss(config: web::Data) -> impl Responder { HttpResponse::Ok() - .append_header(("content-type", MIME_TYPE_RSS)) + .content_type(ContentType(MIME_TYPE_RSS.parse().unwrap())) .body(build_rss(config.get_ref().to_owned())) } #[once(time = 10800)] // 3h fn build_rss(config: Config) -> String { - let mut posts = get_posts(format!("{}/blog", config.locations.data_dir)); + let mut posts = get_posts(format!( + "{}/{}/{}", + config.locations.data_dir, BLOG_DIR, POST_DIR + )); // Sort from newest to oldest posts.sort_by_cached_key(|p| (p.date.year, p.date.month, p.date.day)); diff --git a/src/routes/contact.rs b/src/routes/contact.rs index 77aba1d..5debdb9 100644 --- a/src/routes/contact.rs +++ b/src/routes/contact.rs @@ -13,6 +13,8 @@ use crate::{ template::{Infos, NavBar}, }; +const CONTACT_DIR: &str = "contacts"; + pub fn pages(cfg: &mut web::ServiceConfig) { // Here define the services used let routes = |route_path| { @@ -76,7 +78,7 @@ fn find_links(directory: String) -> Vec { #[get("/{service}/{scope}")] async fn service_redirection(config: web::Data, req: HttpRequest) -> impl Responder { let info = req.match_info(); - let link = find_links(format!("{}/contacts", config.locations.data_dir)) + let link = find_links(format!("{}/{}", config.locations.data_dir, CONTACT_DIR)) .iter() // Find requested service .filter(|&x| x.service == *info.query("service")) @@ -105,6 +107,7 @@ async fn service_redirection(config: web::Data, req: HttpRequest) -> imp #[derive(Content, Debug)] struct NetworksTemplate { navbar: NavBar, + about: Option, socials_exists: bool, socials: Vec, @@ -123,9 +126,15 @@ fn remove_paragraphs(list: &mut [File]) { #[once(time = 60)] fn build_page(config: Config) -> String { - let contacts_dir = format!("{}/contacts", config.locations.data_dir); + let contacts_dir = format!("{}/{}", config.locations.data_dir, CONTACT_DIR); let ext = ".md"; + // Get about + let about = read_file( + &format!("{}/about.md", contacts_dir), + TypeFileMetadata::Generic, + ); + let socials_dir = "socials"; let mut socials = glob(&format!("{contacts_dir}/{socials_dir}/*{ext}")) .unwrap() @@ -156,6 +165,8 @@ fn build_page(config: Config) -> String { contact: true, ..NavBar::default() }, + about, + socials_exists: !socials.is_empty(), socials, diff --git a/src/routes/portfolio.rs b/src/routes/portfolio.rs index 9b3f1ee..b528002 100644 --- a/src/routes/portfolio.rs +++ b/src/routes/portfolio.rs @@ -20,6 +20,7 @@ async fn page(config: web::Data) -> impl Responder { #[derive(Content, Debug)] struct PortfolioTemplate<'a> { navbar: NavBar, + about: Option, location_apps: Option<&'a str>, apps: Option>, archived_apps: Option>, @@ -30,22 +31,29 @@ struct PortfolioTemplate<'a> { #[once(time = 60)] fn build_page(config: Config) -> String { let projects_dir = format!("{}/projects", config.locations.data_dir); + let apps_dir = format!("{}/apps", projects_dir); let ext = ".md"; + // Get about + let about = read_file( + &format!("{}/about.md", projects_dir), + TypeFileMetadata::Generic, + ); + // Get apps - let apps = glob(&format!("{projects_dir}/*{ext}")) + let apps = glob(&format!("{apps_dir}/*{ext}")) .unwrap() .map(|e| read_file(&e.unwrap().to_string_lossy(), TypeFileMetadata::Portfolio).unwrap()) .collect::>(); let appdata = if apps.is_empty() { - (None, Some(projects_dir.as_str())) + (None, Some(apps_dir.as_str())) } else { (Some(apps), None) }; // Get archived apps - let archived_apps = glob(&format!("{projects_dir}/archive/*{ext}")) + let archived_apps = glob(&format!("{apps_dir}/archive/*{ext}")) .unwrap() .map(|e| read_file(&e.unwrap().to_string_lossy(), TypeFileMetadata::Portfolio).unwrap()) .collect::>(); @@ -63,6 +71,7 @@ fn build_page(config: Config) -> String { portfolio: true, ..NavBar::default() }, + about, apps: appdata.0, location_apps: appdata.1, archived_apps: archived_appdata.0, diff --git a/static/icons/safari-pinned-tab.svg b/static/icons/safari-pinned-tab.svg index f6220d6..b7ca3aa 100644 --- a/static/icons/safari-pinned-tab.svg +++ b/static/icons/safari-pinned-tab.svg @@ -1,15 +1,3 @@ - - - - - - - - - - - - - + + diff --git a/static/icons/site.webmanifest b/static/icons/site.webmanifest deleted file mode 100644 index 4fcd7f1..0000000 --- a/static/icons/site.webmanifest +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "Site Anri K.", - "short_name": "Site Anri K.", - "icons": [ - { - "src": "android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - } - ], - "theme_color": "#2a2424", - "background_color": "#2a2424", - "start_url": "https://www.mylloon.fr/", - "display": "standalone" -} diff --git a/templates/app.webmanifest b/templates/app.webmanifest new file mode 100644 index 0000000..e10e8f1 --- /dev/null +++ b/templates/app.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "{{#data}}{{name}}{{/data}}", + "start_url": "{{#data}}{{url}}{{/data}}", + "display": "standalone", + "background_color": "#2a2424", + "description": "{{#data}}{{description}}{{/data}}", + "icons": [ + { + "src": "/icons/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + } + ], + "theme_color": "#2a2424", + "related_applications": [ + { + "platform": "source", + "url": "https://git.mylloon.fr/Anri/mylloon.fr" + } + ] +} diff --git a/templates/blog/index.html b/templates/blog/index.html index ac72f9b..986594d 100644 --- a/templates/blog/index.html +++ b/templates/blog/index.html @@ -16,7 +16,7 @@ {{#data}}

Blog

-

Blog perso, je dis peut-être n'importe quoi 🫶

+ {{#about}} {{&content}} {{/about}} Lien vers le flux RSS {{#no_posts}} diff --git a/templates/contact/index.html b/templates/contact/index.html index cbd8e5e..870d82c 100644 --- a/templates/contact/index.html +++ b/templates/contact/index.html @@ -8,9 +8,8 @@
{{>navbar.html}}

Contact

-

Je suis présent relativement partout sur internet 😸

+ {{#data}}{{#about}} {{&content}} {{/about}} {{#socials_exists}} - {{#data}} {{#socials_exists}}

Réseaux sociaux

    {{#socials}} {{>contact/element.html}} {{/socials}} diff --git a/templates/head.html b/templates/head.html index 36237f9..ee78fc0 100644 --- a/templates/head.html +++ b/templates/head.html @@ -3,6 +3,7 @@ + {{>icons.html}} {{>metadata.html}} diff --git a/templates/icons.html b/templates/icons.html index 506ba18..91c48ee 100644 --- a/templates/icons.html +++ b/templates/icons.html @@ -16,7 +16,6 @@ sizes="16x16" href="/icons/favicon-16x16.png" /> - diff --git a/templates/portfolio/index.html b/templates/portfolio/index.html index d3aa640..b2ce21e 100644 --- a/templates/portfolio/index.html +++ b/templates/portfolio/index.html @@ -10,14 +10,10 @@
    {{#data}}

    Portfolio

    -

    - Je programme depuis 2018 et j'ai appris une multitude de langages - depuis. Étant passionné de logiciels libres depuis que je m'y intéresse, - je publie tout sur des forges publiques. -

    + {{#about}} {{&content}} {{/about}} - {{#location_apps}} + {{#location_apps}}

    {{location_apps}} {{err_msg}}

    {{/location_apps}} {{^location_apps}}