Merge branch 'main' into cours
Some checks are pending
ci/woodpecker/push/publish Pipeline is pending
Some checks are pending
ci/woodpecker/push/publish Pipeline is pending
This commit is contained in:
commit
349b822361
16 changed files with 173 additions and 138 deletions
|
@ -18,6 +18,7 @@ WORKDIR /app
|
||||||
|
|
||||||
COPY --from=builder /usr/local/cargo/bin/ewp /app/ewp
|
COPY --from=builder /usr/local/cargo/bin/ewp /app/ewp
|
||||||
COPY --from=builder /usr/src/ewp/LICENSE /app/LICENSE
|
COPY --from=builder /usr/src/ewp/LICENSE /app/LICENSE
|
||||||
|
COPY --from=builder /usr/src/ewp/README.md /app/README.md
|
||||||
COPY --from=builder /usr/src/ewp/static /app/static
|
COPY --from=builder /usr/src/ewp/static /app/static
|
||||||
COPY --from=builder /usr/src/ewp/templates /app/templates
|
COPY --from=builder /usr/src/ewp/templates /app/templates
|
||||||
|
|
||||||
|
|
|
@ -6,4 +6,4 @@ Easy WebPage generator
|
||||||
[![status-badge](https://ci.mylloon.fr/api/badges/Anri/mylloon.fr/status.svg)](https://ci.mylloon.fr/Anri/mylloon.fr)
|
[![status-badge](https://ci.mylloon.fr/api/badges/Anri/mylloon.fr/status.svg)](https://ci.mylloon.fr/Anri/mylloon.fr)
|
||||||
|
|
||||||
- See [issues](https://git.mylloon.fr/Anri/mylloon.fr/issues)
|
- See [issues](https://git.mylloon.fr/Anri/mylloon.fr/issues)
|
||||||
- See [documentation](./Documentation.md)
|
- See [documentation](https://git.mylloon.fr/Anri/mylloon.fr/src/branch/main/Documentation.md)
|
||||||
|
|
|
@ -69,13 +69,20 @@ impl FileConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Paths where files are stored
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Locations {
|
||||||
|
pub static_dir: String,
|
||||||
|
pub data_dir: String,
|
||||||
|
}
|
||||||
|
|
||||||
/// Configuration used internally in the app
|
/// Configuration used internally in the app
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// Information given in the config file
|
/// Information given in the config file
|
||||||
pub fc: FileConfig,
|
pub fc: FileConfig,
|
||||||
/// Location where the static files are stored
|
/// Location where the static files are stored
|
||||||
pub static_location: String,
|
pub locations: Locations,
|
||||||
/// Informations about templates
|
/// Informations about templates
|
||||||
pub tmpl: Template,
|
pub tmpl: Template,
|
||||||
}
|
}
|
||||||
|
@ -110,7 +117,10 @@ pub fn get_config(file_path: &str) -> Config {
|
||||||
|
|
||||||
Config {
|
Config {
|
||||||
fc: internal_config.to_owned(),
|
fc: internal_config.to_owned(),
|
||||||
static_location: format!("{}/{}", files_root, static_dir),
|
locations: Locations {
|
||||||
|
static_dir: format!("{}/{}", files_root, static_dir),
|
||||||
|
data_dir: String::from("data"),
|
||||||
|
},
|
||||||
tmpl: Template {
|
tmpl: Template {
|
||||||
directory: format!("{}/{}", files_root, templates_dir),
|
directory: format!("{}/{}", files_root, templates_dir),
|
||||||
app_name: internal_config.app_name.unwrap(),
|
app_name: internal_config.app_name.unwrap(),
|
||||||
|
|
|
@ -60,7 +60,7 @@ async fn main() -> Result<()> {
|
||||||
.service(portfolio::page)
|
.service(portfolio::page)
|
||||||
.service(setup::page)
|
.service(setup::page)
|
||||||
.service(web3::page)
|
.service(web3::page)
|
||||||
.service(Files::new("/", config.static_location.to_owned()))
|
.service(Files::new("/", config.locations.static_dir.to_owned()))
|
||||||
.default_service(web::to(not_found::page))
|
.default_service(web::to(not_found::page))
|
||||||
})
|
})
|
||||||
.bind(addr)?
|
.bind(addr)?
|
||||||
|
|
|
@ -6,10 +6,7 @@ use ramhorns::Content;
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
/// Regular markdown files, no metadata
|
/// Metadata for blog posts
|
||||||
#[derive(Content, Debug, Default, Deserialize)]
|
|
||||||
pub struct FileNoMetadata {}
|
|
||||||
|
|
||||||
#[derive(Content, Debug, Default, Deserialize)]
|
#[derive(Content, Debug, Default, Deserialize)]
|
||||||
pub struct FileMetadataBlog {
|
pub struct FileMetadataBlog {
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
|
@ -20,39 +17,7 @@ pub struct FileMetadataBlog {
|
||||||
pub toc: Option<bool>,
|
pub toc: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Content, Debug, Default, Deserialize)]
|
/// A tag, related to post blog
|
||||||
pub struct FileMetadataContact {
|
|
||||||
pub title: String,
|
|
||||||
pub custom: Option<bool>,
|
|
||||||
pub user: Option<String>,
|
|
||||||
pub link: Option<String>,
|
|
||||||
pub newtab: Option<bool>,
|
|
||||||
pub description: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Content, Debug, Default, Deserialize)]
|
|
||||||
pub struct FileMetadataPortfolio {
|
|
||||||
pub title: Option<String>,
|
|
||||||
pub link: Option<String>,
|
|
||||||
pub description: Option<String>,
|
|
||||||
pub language: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum TypeFileMetadata {
|
|
||||||
Blog,
|
|
||||||
Contact,
|
|
||||||
Portfolio,
|
|
||||||
Cours,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Content, Debug, Default, Deserialize)]
|
|
||||||
pub struct FileMetadata {
|
|
||||||
pub blog: Option<FileMetadataBlog>,
|
|
||||||
pub contact: Option<FileMetadataContact>,
|
|
||||||
pub portfolio: Option<FileMetadataPortfolio>,
|
|
||||||
pub cours: Option<FileNoMetadata>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Content, Debug, Clone)]
|
#[derive(Content, Debug, Clone)]
|
||||||
pub struct Tag {
|
pub struct Tag {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -73,6 +38,55 @@ impl<'de> Deserialize<'de> for Tag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Metadata for contact entry
|
||||||
|
#[derive(Content, Debug, Default, Deserialize)]
|
||||||
|
pub struct FileMetadataContact {
|
||||||
|
pub title: String,
|
||||||
|
pub custom: Option<bool>,
|
||||||
|
pub user: Option<String>,
|
||||||
|
pub link: Option<String>,
|
||||||
|
pub newtab: Option<bool>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Metadata for index page
|
||||||
|
#[derive(Content, Debug, Default, Deserialize)]
|
||||||
|
pub struct FileMetadataIndex {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub pronouns: Option<String>,
|
||||||
|
pub avatar: Option<String>,
|
||||||
|
pub avatar_caption: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Metadata for portfolio cards
|
||||||
|
#[derive(Content, Debug, Default, Deserialize)]
|
||||||
|
pub struct FileMetadataPortfolio {
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub link: Option<String>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub language: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List of available metadata types
|
||||||
|
pub enum TypeFileMetadata {
|
||||||
|
Blog,
|
||||||
|
Contact,
|
||||||
|
Generic,
|
||||||
|
Index,
|
||||||
|
Portfolio,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Structure who holds all the metadata the file have
|
||||||
|
/// Usually all fields are None except one
|
||||||
|
#[derive(Content, Debug, Default, Deserialize)]
|
||||||
|
pub struct FileMetadata {
|
||||||
|
pub blog: Option<FileMetadataBlog>,
|
||||||
|
pub contact: Option<FileMetadataContact>,
|
||||||
|
pub index: Option<FileMetadataIndex>,
|
||||||
|
pub portfolio: Option<FileMetadataPortfolio>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Global metadata
|
||||||
#[derive(Content, Debug)]
|
#[derive(Content, Debug)]
|
||||||
pub struct Metadata {
|
pub struct Metadata {
|
||||||
pub info: FileMetadata,
|
pub info: FileMetadata,
|
||||||
|
@ -81,6 +95,7 @@ pub struct Metadata {
|
||||||
pub syntax_highlight: bool,
|
pub syntax_highlight: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// File description
|
||||||
#[derive(Content, Debug)]
|
#[derive(Content, Debug)]
|
||||||
pub struct File {
|
pub struct File {
|
||||||
pub metadata: Metadata,
|
pub metadata: Metadata,
|
||||||
|
@ -220,6 +235,7 @@ pub fn get_metadata<'a>(root: &'a AstNode<'a>, mtype: TypeFileMetadata) -> FileM
|
||||||
match root
|
match root
|
||||||
.children()
|
.children()
|
||||||
.find_map(|node| match &node.data.borrow().value {
|
.find_map(|node| match &node.data.borrow().value {
|
||||||
|
// Extract metadata from frontmatter
|
||||||
NodeValue::FrontMatter(text) => Some(match mtype {
|
NodeValue::FrontMatter(text) => Some(match mtype {
|
||||||
TypeFileMetadata::Blog => FileMetadata {
|
TypeFileMetadata::Blog => FileMetadata {
|
||||||
blog: Some(deserialize_metadata(text)),
|
blog: Some(deserialize_metadata(text)),
|
||||||
|
@ -238,18 +254,22 @@ pub fn get_metadata<'a>(root: &'a AstNode<'a>, mtype: TypeFileMetadata) -> FileM
|
||||||
..FileMetadata::default()
|
..FileMetadata::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TypeFileMetadata::Portfolio => FileMetadata {
|
TypeFileMetadata::Generic => FileMetadata {
|
||||||
portfolio: Some(deserialize_metadata(text)),
|
|
||||||
..FileMetadata::default()
|
..FileMetadata::default()
|
||||||
},
|
},
|
||||||
TypeFileMetadata::Cours => FileMetadata {
|
TypeFileMetadata::Index => FileMetadata {
|
||||||
cours: Some(deserialize_metadata(text)),
|
index: Some(deserialize_metadata(text)),
|
||||||
|
..FileMetadata::default()
|
||||||
|
},
|
||||||
|
TypeFileMetadata::Portfolio => FileMetadata {
|
||||||
|
portfolio: Some(deserialize_metadata(text)),
|
||||||
..FileMetadata::default()
|
..FileMetadata::default()
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
}) {
|
}) {
|
||||||
Some(data) => data,
|
Some(data) => data,
|
||||||
|
// No metadata
|
||||||
None => match mtype {
|
None => match mtype {
|
||||||
TypeFileMetadata::Blog => FileMetadata {
|
TypeFileMetadata::Blog => FileMetadata {
|
||||||
blog: Some(FileMetadataBlog::default()),
|
blog: Some(FileMetadataBlog::default()),
|
||||||
|
@ -259,12 +279,15 @@ pub fn get_metadata<'a>(root: &'a AstNode<'a>, mtype: TypeFileMetadata) -> FileM
|
||||||
contact: Some(FileMetadataContact::default()),
|
contact: Some(FileMetadataContact::default()),
|
||||||
..FileMetadata::default()
|
..FileMetadata::default()
|
||||||
},
|
},
|
||||||
TypeFileMetadata::Portfolio => FileMetadata {
|
TypeFileMetadata::Generic => FileMetadata {
|
||||||
portfolio: Some(FileMetadataPortfolio::default()),
|
|
||||||
..FileMetadata::default()
|
..FileMetadata::default()
|
||||||
},
|
},
|
||||||
TypeFileMetadata::Cours => FileMetadata {
|
TypeFileMetadata::Index => FileMetadata {
|
||||||
cours: Some(FileNoMetadata::default()),
|
index: Some(FileMetadataIndex::default()),
|
||||||
|
..FileMetadata::default()
|
||||||
|
},
|
||||||
|
TypeFileMetadata::Portfolio => FileMetadata {
|
||||||
|
portfolio: Some(FileMetadataPortfolio::default()),
|
||||||
..FileMetadata::default()
|
..FileMetadata::default()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use actix_web::{get, HttpResponse, Responder};
|
use actix_web::{get, HttpResponse, Responder};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
/// Response
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct Info {
|
struct Info {
|
||||||
unix_epoch: u32,
|
unix_epoch: u32,
|
||||||
|
|
|
@ -42,7 +42,7 @@ struct BlogIndexTemplate {
|
||||||
|
|
||||||
#[once(time = 60)]
|
#[once(time = 60)]
|
||||||
fn build_index(config: Config) -> String {
|
fn build_index(config: Config) -> String {
|
||||||
let mut posts = get_posts("data/blog");
|
let mut posts = get_posts(format!("{}/blog", config.locations.data_dir));
|
||||||
|
|
||||||
// Sort from newest to oldest
|
// Sort from newest to oldest
|
||||||
posts.sort_by_cached_key(|p| (p.date.year, p.date.month, p.date.day));
|
posts.sort_by_cached_key(|p| (p.date.year, p.date.month, p.date.day));
|
||||||
|
@ -81,8 +81,8 @@ struct Post {
|
||||||
|
|
||||||
impl Post {
|
impl Post {
|
||||||
// Fetch the file content
|
// Fetch the file content
|
||||||
fn fetch_content(&mut self) {
|
fn fetch_content(&mut self, data_dir: &str) {
|
||||||
let blog_dir = "data/blog";
|
let blog_dir = format!("{}/blog", data_dir);
|
||||||
let ext = ".md";
|
let ext = ".md";
|
||||||
|
|
||||||
if let Some(file) = read_file(
|
if let Some(file) = read_file(
|
||||||
|
@ -102,8 +102,8 @@ impl Hash for Post {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_posts(location: &str) -> Vec<Post> {
|
fn get_posts(location: String) -> Vec<Post> {
|
||||||
let entries = match std::fs::read_dir(location) {
|
let entries = match std::fs::read_dir(&location) {
|
||||||
Ok(res) => res
|
Ok(res) => res
|
||||||
.flatten()
|
.flatten()
|
||||||
.filter(|f| match f.path().extension() {
|
.filter(|f| match f.path().extension() {
|
||||||
|
@ -190,7 +190,12 @@ async fn page(path: web::Path<(String,)>, config: web::Data<Config>) -> impl Res
|
||||||
|
|
||||||
fn build_post(file: String, config: Config) -> String {
|
fn build_post(file: String, config: Config) -> String {
|
||||||
let mut post = None;
|
let mut post = None;
|
||||||
let (infos, toc) = get_post(&mut post, file, config.fc.name.unwrap_or_default());
|
let (infos, toc) = get_post(
|
||||||
|
&mut post,
|
||||||
|
file,
|
||||||
|
config.fc.name.unwrap_or_default(),
|
||||||
|
config.locations.data_dir,
|
||||||
|
);
|
||||||
|
|
||||||
config.tmpl.render(
|
config.tmpl.render(
|
||||||
"blog/post.html",
|
"blog/post.html",
|
||||||
|
@ -206,8 +211,13 @@ fn build_post(file: String, config: Config) -> String {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_post(post: &mut Option<File>, filename: String, name: String) -> (Infos, String) {
|
fn get_post(
|
||||||
let blog_dir = "data/blog";
|
post: &mut Option<File>,
|
||||||
|
filename: String,
|
||||||
|
name: String,
|
||||||
|
data_dir: String,
|
||||||
|
) -> (Infos, String) {
|
||||||
|
let blog_dir = format!("{}/blog", data_dir);
|
||||||
let ext = ".md";
|
let ext = ".md";
|
||||||
|
|
||||||
*post = read_file(
|
*post = read_file(
|
||||||
|
@ -268,7 +278,7 @@ async fn rss(config: web::Data<Config>) -> impl Responder {
|
||||||
|
|
||||||
#[once(time = 10800)] // 3h
|
#[once(time = 10800)] // 3h
|
||||||
fn build_rss(config: Config) -> String {
|
fn build_rss(config: Config) -> String {
|
||||||
let mut posts = get_posts("data/blog");
|
let mut posts = get_posts(format!("{}/blog", config.locations.data_dir));
|
||||||
|
|
||||||
// Sort from newest to oldest
|
// Sort from newest to oldest
|
||||||
posts.sort_by_cached_key(|p| (p.date.year, p.date.month, p.date.day));
|
posts.sort_by_cached_key(|p| (p.date.year, p.date.month, p.date.day));
|
||||||
|
@ -315,7 +325,7 @@ fn build_rss(config: Config) -> String {
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.map(|p| {
|
.map(|p| {
|
||||||
// Get post data
|
// Get post data
|
||||||
p.fetch_content();
|
p.fetch_content(&config.locations.data_dir);
|
||||||
|
|
||||||
// Build item
|
// Build item
|
||||||
Item {
|
Item {
|
||||||
|
|
|
@ -30,6 +30,7 @@ async fn page(config: web::Data<Config>) -> impl Responder {
|
||||||
Html(build_page(config.get_ref().to_owned()))
|
Html(build_page(config.get_ref().to_owned()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Contact node
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct ContactLink {
|
struct ContactLink {
|
||||||
service: String,
|
service: String,
|
||||||
|
@ -38,13 +39,12 @@ struct ContactLink {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[once(time = 60)]
|
#[once(time = 60)]
|
||||||
fn find_links() -> Vec<ContactLink> {
|
fn find_links(directory: String) -> Vec<ContactLink> {
|
||||||
// TOML file location
|
// TOML filename
|
||||||
let contacts_dir = "data/contacts";
|
|
||||||
let toml_file = "links.toml";
|
let toml_file = "links.toml";
|
||||||
|
|
||||||
// Read the TOML file and parse it
|
// Read the TOML file and parse it
|
||||||
let toml_str = read_to_string(format!("{contacts_dir}/{toml_file}")).unwrap_or_default();
|
let toml_str = read_to_string(format!("{directory}/{toml_file}")).unwrap_or_default();
|
||||||
|
|
||||||
let mut redirections = vec![];
|
let mut redirections = vec![];
|
||||||
match toml::de::from_str::<toml::Value>(&toml_str) {
|
match toml::de::from_str::<toml::Value>(&toml_str) {
|
||||||
|
@ -74,9 +74,9 @@ fn find_links() -> Vec<ContactLink> {
|
||||||
#[routes]
|
#[routes]
|
||||||
#[get("/{service}")]
|
#[get("/{service}")]
|
||||||
#[get("/{service}/{scope}")]
|
#[get("/{service}/{scope}")]
|
||||||
async fn service_redirection(req: HttpRequest) -> impl Responder {
|
async fn service_redirection(config: web::Data<Config>, req: HttpRequest) -> impl Responder {
|
||||||
let info = req.match_info();
|
let info = req.match_info();
|
||||||
let link = find_links()
|
let link = find_links(format!("{}/contacts", config.locations.data_dir))
|
||||||
.iter()
|
.iter()
|
||||||
// Find requested service
|
// Find requested service
|
||||||
.filter(|&x| x.service == *info.query("service"))
|
.filter(|&x| x.service == *info.query("service"))
|
||||||
|
@ -123,7 +123,7 @@ fn remove_paragraphs(list: &mut [File]) {
|
||||||
|
|
||||||
#[once(time = 60)]
|
#[once(time = 60)]
|
||||||
fn build_page(config: Config) -> String {
|
fn build_page(config: Config) -> String {
|
||||||
let contacts_dir = "data/contacts";
|
let contacts_dir = format!("{}/contacts", config.locations.data_dir);
|
||||||
let ext = ".md";
|
let ext = ".md";
|
||||||
|
|
||||||
let socials_dir = "socials";
|
let socials_dir = "socials";
|
||||||
|
|
|
@ -116,20 +116,20 @@ async fn build_page(config: Config) -> String {
|
||||||
error: false,
|
error: false,
|
||||||
projects: Some(
|
projects: Some(
|
||||||
data.iter()
|
data.iter()
|
||||||
|
.filter(|&p| !p.pulls_merged.is_empty())
|
||||||
.cloned()
|
.cloned()
|
||||||
.filter(|p| !p.pulls_merged.is_empty())
|
|
||||||
.collect(),
|
.collect(),
|
||||||
),
|
),
|
||||||
waiting: Some(
|
waiting: Some(
|
||||||
data.iter()
|
data.iter()
|
||||||
|
.filter(|&p| !p.pulls_open.is_empty())
|
||||||
.cloned()
|
.cloned()
|
||||||
.filter(|p| !p.pulls_open.is_empty())
|
|
||||||
.collect(),
|
.collect(),
|
||||||
),
|
),
|
||||||
closed: Some(
|
closed: Some(
|
||||||
data.iter()
|
data.iter()
|
||||||
|
.filter(|&p| !p.pulls_closed.is_empty())
|
||||||
.cloned()
|
.cloned()
|
||||||
.filter(|p| !p.pulls_closed.is_empty())
|
|
||||||
.collect(),
|
.collect(),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,7 +91,10 @@ fn get_content(
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
read_file(&format!("{cours_dir}/{filename}"), TypeFileMetadata::Cours)
|
read_file(
|
||||||
|
&format!("{cours_dir}/{filename}"),
|
||||||
|
TypeFileMetadata::Generic,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[once(time = 60)]
|
// #[once(time = 60)]
|
||||||
|
|
|
@ -4,7 +4,10 @@ use ramhorns::Content;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::Config,
|
config::Config,
|
||||||
misc::utils::{make_kw, Html},
|
misc::{
|
||||||
|
markdown::{read_file, File, TypeFileMetadata},
|
||||||
|
utils::{make_kw, Html},
|
||||||
|
},
|
||||||
template::{Infos, NavBar},
|
template::{Infos, NavBar},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -16,11 +19,37 @@ async fn page(config: web::Data<Config>) -> impl Responder {
|
||||||
#[derive(Content, Debug)]
|
#[derive(Content, Debug)]
|
||||||
struct IndexTemplate {
|
struct IndexTemplate {
|
||||||
navbar: NavBar,
|
navbar: NavBar,
|
||||||
fullname: String,
|
name: String,
|
||||||
|
pronouns: Option<String>,
|
||||||
|
content: Option<File>,
|
||||||
|
avatar: String,
|
||||||
|
avatar_caption: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[once(time = 60)]
|
#[once(time = 60)]
|
||||||
fn build_page(config: Config) -> String {
|
fn build_page(config: Config) -> String {
|
||||||
|
let mut file = read_file(
|
||||||
|
&format!("{}/index.md", config.locations.data_dir),
|
||||||
|
TypeFileMetadata::Index,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Default values
|
||||||
|
let mut name = config.fc.fullname.to_owned().unwrap_or_default();
|
||||||
|
let mut pronouns = None;
|
||||||
|
let mut avatar = "/icons/apple-touch-icon.png".to_owned();
|
||||||
|
let mut avatar_caption = "EWP avatar".to_owned();
|
||||||
|
|
||||||
|
if let Some(f) = &file {
|
||||||
|
if let Some(m) = &f.metadata.info.index {
|
||||||
|
name = m.name.to_owned().unwrap_or(name);
|
||||||
|
avatar = m.avatar.to_owned().unwrap_or(avatar);
|
||||||
|
pronouns = m.pronouns.to_owned();
|
||||||
|
avatar_caption = m.avatar_caption.to_owned().unwrap_or(avatar_caption);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
file = read_file("README.md", TypeFileMetadata::Generic);
|
||||||
|
}
|
||||||
|
|
||||||
config.tmpl.render(
|
config.tmpl.render(
|
||||||
"index.html",
|
"index.html",
|
||||||
IndexTemplate {
|
IndexTemplate {
|
||||||
|
@ -28,16 +57,16 @@ fn build_page(config: Config) -> String {
|
||||||
index: true,
|
index: true,
|
||||||
..NavBar::default()
|
..NavBar::default()
|
||||||
},
|
},
|
||||||
fullname: config
|
content: file,
|
||||||
.fc
|
name,
|
||||||
.fullname
|
pronouns,
|
||||||
.to_owned()
|
avatar,
|
||||||
.unwrap_or("Fullname".to_owned()),
|
avatar_caption,
|
||||||
},
|
},
|
||||||
Infos {
|
Infos {
|
||||||
page_title: config.fc.fullname,
|
page_title: config.fc.fullname,
|
||||||
page_desc: Some("Page principale".into()),
|
page_desc: Some("Page principale".into()),
|
||||||
page_kw: make_kw(&["index", "étudiant"]),
|
page_kw: make_kw(&["index", "étudiant", "accueil"]),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ struct PortfolioTemplate<'a> {
|
||||||
|
|
||||||
#[once(time = 60)]
|
#[once(time = 60)]
|
||||||
fn build_page(config: Config) -> String {
|
fn build_page(config: Config) -> String {
|
||||||
let projects_dir = "data/projects";
|
let projects_dir = format!("{}/projects", config.locations.data_dir);
|
||||||
let ext = ".md";
|
let ext = ".md";
|
||||||
|
|
||||||
// Get apps
|
// Get apps
|
||||||
|
@ -39,7 +39,7 @@ fn build_page(config: Config) -> String {
|
||||||
.collect::<Vec<File>>();
|
.collect::<Vec<File>>();
|
||||||
|
|
||||||
let appdata = if apps.is_empty() {
|
let appdata = if apps.is_empty() {
|
||||||
(None, Some(projects_dir))
|
(None, Some(projects_dir.as_str()))
|
||||||
} else {
|
} else {
|
||||||
(Some(apps), None)
|
(Some(apps), None)
|
||||||
};
|
};
|
||||||
|
|
|
@ -24,6 +24,7 @@ pub struct Infos {
|
||||||
pub page_kw: Option<String>,
|
pub page_kw: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Information on what page the user is currently
|
||||||
#[derive(Content, Debug, Default)]
|
#[derive(Content, Debug, Default)]
|
||||||
pub struct NavBar {
|
pub struct NavBar {
|
||||||
pub index: bool,
|
pub index: bool,
|
||||||
|
|
BIN
static/badges/friends/jas.webp
(Stored with Git LFS)
Normal file
BIN
static/badges/friends/jas.webp
(Stored with Git LFS)
Normal file
Binary file not shown.
|
@ -64,8 +64,8 @@ h1 {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#friends a {
|
#friends a:not(h1 > a) {
|
||||||
padding-right: 10px;
|
padding-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#friends h1 {
|
#friends h1 {
|
||||||
|
|
|
@ -10,70 +10,24 @@
|
||||||
{{#data}}
|
{{#data}}
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<span id="name">{{fullname}}</span>
|
<span id="name">{{name}}</span>
|
||||||
<span id="pronouns">(il/lui, he/him)</span>
|
{{#pronouns}}<span id="pronouns">{{pronouns}}</span>{{/pronouns}}
|
||||||
<img
|
<img
|
||||||
id="avatar"
|
id="avatar"
|
||||||
src="/icons/apple-touch-icon.png"
|
src="{{avatar}} "
|
||||||
alt="Avatar"
|
alt="Avatar"
|
||||||
title="Mon avatar, dessiné un jour super rapidement sur Gimp."
|
title="{{avatar_caption}} "
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p id="subname"></p>
|
<p id="subname"></p>
|
||||||
|
|
||||||
<article>
|
{{#content}} {{&content}} {{/content}} {{^content}}
|
||||||
<h1>Qui suis-je ?</h1>
|
<p>
|
||||||
<p>Je m'appelle <b>Anri</b>, mon pseudo est <b>Mylloon</b>.</p>
|
<b>Welcome to EWP</b>, create a <code>index.md</code> file inside your
|
||||||
<p>
|
<code>data/</code> directory to get started.
|
||||||
J'aime beaucoup l'informatique depuis très petit, ce site est écrit de
|
</p>
|
||||||
A à Z par moi-même (modulo la quantité astronomique de librairie
|
{{/content}} {{/data}}
|
||||||
utilisé) en Rust. J'adore le monde de l'open source, l'immense
|
|
||||||
majorité de mes projets sont sous licence
|
|
||||||
<a
|
|
||||||
href="https://en.wikipedia.org/wiki/GNU_Affero_General_Public_License"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>AGPLv3</a
|
|
||||||
>.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
En ce moment, je suis en master d'informatique à Paris Cité
|
|
||||||
(anciennement Paris 7), c'est marrant on fait de l'OCaml 🤓☝️.
|
|
||||||
</p>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
<article id="friends">
|
|
||||||
<h1>Personnes incroyables</h1>
|
|
||||||
<a
|
|
||||||
href="https://github.com/2-1-1-2"
|
|
||||||
title="GitHub de 21_12"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
><img src="/badges/friends/21_12.webp" alt="21_12" loading="lazy"
|
|
||||||
/></a>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="https://twitter.com/azazouille_"
|
|
||||||
title="Twitter de Azazouille"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
><img
|
|
||||||
src="/badges/friends/azazouille.webp"
|
|
||||||
alt="Azazouille"
|
|
||||||
loading="lazy"
|
|
||||||
/></a>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="https://102jjwy.carrd.co/"
|
|
||||||
title="Carrd de 102jjwy"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
><img src="/badges/friends/102jjwy.webp" alt="102jjwy" loading="lazy"
|
|
||||||
/></a>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
{{/data}}
|
|
||||||
</main>
|
</main>
|
||||||
<script src="/js/index.js"></script>
|
<script src="/js/index.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
Loading…
Reference in a new issue