Merge branch 'main' into cours
Some checks are pending
ci/woodpecker/push/publish Pipeline is pending

This commit is contained in:
Mylloon 2024-01-25 18:31:42 +01:00
commit 775c5bb7bc
Signed by: Anri
GPG key ID: A82D63DFF8D1317F
16 changed files with 150 additions and 66 deletions

12
Cargo.lock generated
View file

@ -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"

View file

@ -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"] }

View file

@ -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<String>
pronouns: Option<String>
avatar: Option<String>
avatar_caption: Option<String>
---
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 <!-- omit in toc -->
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 <!-- omit in toc -->
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 <!-- omit in toc -->
The file is stored at `/app/data/contacts/about.md`.
## Courses
Markdown files are stored in `/app/data/cours/`

View file

@ -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)

View file

@ -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<Config>) -> 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(),
)
}

View file

@ -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<Config>) -> impl Responder {
@ -36,13 +38,19 @@ async fn index(config: web::Data<Config>) -> impl Responder {
#[derive(Content, Debug)]
struct BlogIndexTemplate {
navbar: NavBar,
about: Option<File>,
posts: Vec<Post>,
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<File> =
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<Config>) -> 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));

View file

@ -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<ContactLink> {
#[get("/{service}/{scope}")]
async fn service_redirection(config: web::Data<Config>, 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<Config>, req: HttpRequest) -> imp
#[derive(Content, Debug)]
struct NetworksTemplate {
navbar: NavBar,
about: Option<File>,
socials_exists: bool,
socials: Vec<File>,
@ -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,

View file

@ -20,6 +20,7 @@ async fn page(config: web::Data<Config>) -> impl Responder {
#[derive(Content, Debug)]
struct PortfolioTemplate<'a> {
navbar: NavBar,
about: Option<File>,
location_apps: Option<&'a str>,
apps: Option<Vec<File>>,
archived_apps: Option<Vec<File>>,
@ -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::<Vec<File>>();
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::<Vec<File>>();
@ -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,

View file

@ -1,15 +1,3 @@
<?xml version="1.0" standalone="no"?>
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="180.000000pt" height="180.000000pt" viewBox="0 0 180.000000 180.000000" preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,180.000000) scale(0.100000,-0.100000)" fill="#000000" stroke="none">
<path d="M380 1780 c0 -47 56 -117 85 -107 19 8 19 47 1 92 -13 31 -19 35 -50 35 -30 0 -36 -4 -36 -20z"/>
<path d="M1220 1740 c-34 -34 -17 -127 26 -144 27 -10 39 10 36 65 -3 89 -25 116 -62 79z"/>
<path d="M789 1729 c-22 -41 12 -119 51 -119 25 0 32 26 19 74 -18 68 -47 87 -70 45z"/>
<path d="M0 1683 c0 -55 3 -64 26 -82 33 -26 41 -26 54 0 15 28 -1 66 -46 110 l-34 33 0 -61z"/>
<path d="M1563 1686 c-20 -43 -11 -86 18 -86 25 0 53 62 45 96 -9 36 -43 31 -63 -10z"/>
<path d="M343 1395 c-47 -33 -37 -116 19 -158 36 -27 74 -16 95 27 41 87 -42 182 -114 131z"/>
<path d="M1313 1382 c-12 -9 -27 -32 -34 -49 -23 -69 52 -153 138 -153 84 0 119 70 76 153 -31 60 -131 87 -180 49z"/>
<path d="M862 998 c-19 -19 -14 -46 16 -91 16 -23 32 -50 35 -60 4 -9 18 -22 32 -29 22 -10 30 -8 53 9 15 11 28 32 30 47 9 73 -120 170 -166 124z"/>
<path d="M1428 779 c-29 -16 -21 -63 27 -159 82 -164 65 -189 -173 -247 -192 -47 -434 -29 -646 48 -108 39 -160 83 -181 152 -16 54 -27 67 -61 67 -32 0 -54 -31 -54 -76 0 -63 50 -138 122 -184 178 -116 546 -178 784 -134 30 6 34 2 88 -75 61 -86 85 -106 116 -96 43 14 33 73 -28 152 -13 18 -22 39 -19 46 3 8 33 24 68 37 111 42 159 111 146 211 -7 54 -91 223 -123 248 -30 23 -41 25 -66 10z"/>
</g>
<svg xmlns="http://www.w3.org/2000/svg" width="240" height="240" version="1.0" viewBox="0 0 180 180">
<path d="M38 2c0 4.7 5.6 11.7 8.5 10.7 1.9-.8 1.9-4.7.1-9.2C45.3.4 44.7 0 41.6 0c-3 0-3.6.4-3.6 2zM122 6c-3.4 3.4-1.7 12.7 2.6 14.4 2.7 1 3.9-1 3.6-6.5-.3-8.9-2.5-11.6-6.2-7.9zM78.9 7.1C76.7 11.2 80.1 19 84 19c2.5 0 3.2-2.6 1.9-7.4-1.8-6.8-4.7-8.7-7-4.5zM0 11.7c0 5.5.3 6.4 2.6 8.2 3.3 2.6 4.1 2.6 5.4 0 1.5-2.8-.1-6.6-4.6-11L0 5.6v6.1zM156.3 11.4c-2 4.3-1.1 8.6 1.8 8.6 2.5 0 5.3-6.2 4.5-9.6-.9-3.6-4.3-3.1-6.3 1zM34.3 40.5c-4.7 3.3-3.7 11.6 1.9 15.8 3.6 2.7 7.4 1.6 9.5-2.7 4.1-8.7-4.2-18.2-11.4-13.1zM131.3 41.8c-1.2.9-2.7 3.2-3.4 4.9-2.3 6.9 5.2 15.3 13.8 15.3 8.4 0 11.9-7 7.6-15.3-3.1-6-13.1-8.7-18-4.9zM86.2 80.2c-1.9 1.9-1.4 4.6 1.6 9.1 1.6 2.3 3.2 5 3.5 6 .4.9 1.8 2.2 3.2 2.9 2.2 1 3 .8 5.3-.9 1.5-1.1 2.8-3.2 3-4.7.9-7.3-12-17-16.6-12.4zM142.8 102.1c-2.9 1.6-2.1 6.3 2.7 15.9 8.2 16.4 6.5 18.9-17.3 24.7-19.2 4.7-43.4 2.9-64.6-4.8-10.8-3.9-16-8.3-18.1-15.2-1.6-5.4-2.7-6.7-6.1-6.7-3.2 0-5.4 3.1-5.4 7.6 0 6.3 5 13.8 12.2 18.4 17.8 11.6 54.6 17.8 78.4 13.4 3-.6 3.4-.2 8.8 7.5 6.1 8.6 8.5 10.6 11.6 9.6 4.3-1.4 3.3-7.3-2.8-15.2-1.3-1.8-2.2-3.9-1.9-4.6.3-.8 3.3-2.4 6.8-3.7 11.1-4.2 15.9-11.1 14.6-21.1-.7-5.4-9.1-22.3-12.3-24.8-3-2.3-4.1-2.5-6.6-1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -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"
}

21
templates/app.webmanifest Normal file
View file

@ -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"
}
]
}

View file

@ -16,7 +16,7 @@
{{#data}}
<h1>Blog</h1>
<p>Blog perso, je dis peut-être n'importe quoi 🫶</p>
{{#about}} {{&content}} {{/about}}
<a id="rss" href="/blog/rss">Lien vers le flux RSS</a>
{{#no_posts}}

View file

@ -8,9 +8,8 @@
<header>{{>navbar.html}}</header>
<main>
<h1>Contact</h1>
<p>Je suis présent relativement partout sur internet 😸</p>
{{#data}}{{#about}} {{&content}} {{/about}} {{#socials_exists}}
{{#data}} {{#socials_exists}}
<h2>Réseaux sociaux</h2>
<ul>
{{#socials}} {{>contact/element.html}} {{/socials}}

View file

@ -3,6 +3,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="author" href="/humans.txt" />
<link rel="manifest" href="/app.webmanifest" />
{{>icons.html}} {{>metadata.html}}

View file

@ -16,7 +16,6 @@
sizes="16x16"
href="/icons/favicon-16x16.png"
/>
<link rel="manifest" href="/icons/site.webmanifest" />
<link rel="mask-icon" href="/icons/safari-pinned-tab.svg" color="#5bbad5" />
<link rel="shortcut icon" href="/icons/favicon.ico" />
<meta name="msapplication-TileColor" content="#ffffff" />

View file

@ -10,14 +10,10 @@
<main>
{{#data}}
<h1>Portfolio</h1>
<p>
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.
</p>
{{#about}} {{&content}} {{/about}}
{{#location_apps}}
<!-- Error message -->
{{#location_apps}}
<p>{{location_apps}} {{err_msg}}</p>
{{/location_apps}} {{^location_apps}}