Compare commits
8 commits
svg-select
...
main
Author | SHA1 | Date | |
---|---|---|---|
fed97a269b | |||
f725318f56 | |||
77c140a889 | |||
017f04c15e | |||
ca12c5c467 | |||
e699d3cbac | |||
07cd3b1ed4 | |||
c523993ba5 |
34 changed files with 364 additions and 278 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -7,13 +7,13 @@ docker-compose.yml
|
||||||
/.vscode
|
/.vscode
|
||||||
|
|
||||||
# Data
|
# Data
|
||||||
data/index.md
|
data/index*.md
|
||||||
|
|
||||||
data/contacts/*
|
data/contacts/*
|
||||||
data/cours/*
|
data/cours/*
|
||||||
data/projects/*
|
data/projects/*
|
||||||
|
|
||||||
# Blog
|
# Blog
|
||||||
data/blog/*.md
|
data/blog/about*.md
|
||||||
data/blog/posts/*
|
data/blog/posts/*
|
||||||
!data/blog/posts/Makefile
|
!data/blog/posts/Makefile
|
||||||
|
|
154
Cargo.lock
generated
154
Cargo.lock
generated
|
@ -85,7 +85,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
|
checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -199,7 +199,7 @@ dependencies = [
|
||||||
"actix-router",
|
"actix-router",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -346,9 +346,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.94"
|
version = "1.0.95"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
|
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
|
@ -364,14 +364,14 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atom_syndication"
|
name = "atom_syndication"
|
||||||
version = "0.12.5"
|
version = "0.12.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3ee79fb83c725eae67b55218870813d2fc39fd85e4f1583848ef9f4f823cfe7c"
|
checksum = "ec03a6e158ee0f38bfba811976ae909bc2505a4a2f4049c7e8df47df3497b119"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"derive_builder",
|
"derive_builder",
|
||||||
|
@ -507,7 +507,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"rustversion",
|
"rustversion",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -607,7 +607,7 @@ dependencies = [
|
||||||
"darling",
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -628,9 +628,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.4"
|
version = "1.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf"
|
checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"shlex",
|
"shlex",
|
||||||
]
|
]
|
||||||
|
@ -706,7 +706,7 @@ dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -723,9 +723,9 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "comrak"
|
name = "comrak"
|
||||||
version = "0.31.0"
|
version = "0.32.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "453dcb42e33f7b474d7e0db12e0b8d82802c88f35cf5a1d8c297d0dfcecb154f"
|
checksum = "48ae8f3e7e3f3d424cbb33354fc36943d507327d210aa5794b0192f4be726c6d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bon",
|
"bon",
|
||||||
"caseless",
|
"caseless",
|
||||||
|
@ -813,9 +813,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-deque"
|
name = "crossbeam-deque"
|
||||||
version = "0.8.5"
|
version = "0.8.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
|
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-epoch",
|
"crossbeam-epoch",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
|
@ -832,9 +832,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-common"
|
name = "crypto-common"
|
||||||
|
@ -892,7 +892,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
|
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -922,7 +922,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"strsim",
|
"strsim",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -933,7 +933,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core",
|
"darling_core",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -991,7 +991,7 @@ dependencies = [
|
||||||
"darling",
|
"darling",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1001,7 +1001,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
|
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"derive_builder_core",
|
"derive_builder_core",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1014,7 +1014,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1050,7 +1050,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1089,6 +1089,29 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
|
checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_filter"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.11.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"env_filter",
|
||||||
|
"humantime",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -1117,6 +1140,7 @@ dependencies = [
|
||||||
"chrono-tz",
|
"chrono-tz",
|
||||||
"comrak",
|
"comrak",
|
||||||
"cyborgtime",
|
"cyborgtime",
|
||||||
|
"env_logger",
|
||||||
"glob",
|
"glob",
|
||||||
"lol_html",
|
"lol_html",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
|
@ -1168,9 +1192,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foldhash"
|
name = "foldhash"
|
||||||
version = "0.1.3"
|
version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2"
|
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "foreign-types"
|
name = "foreign-types"
|
||||||
|
@ -1445,10 +1469,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "humantime"
|
||||||
version = "1.5.1"
|
version = "2.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f"
|
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper"
|
||||||
|
version = "1.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
|
@ -1466,9 +1496,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-rustls"
|
name = "hyper-rustls"
|
||||||
version = "0.27.3"
|
version = "0.27.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
|
checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http 1.2.0",
|
"http 1.2.0",
|
||||||
|
@ -1654,7 +1684,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1772,9 +1802,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.168"
|
version = "0.2.169"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
|
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libyml"
|
name = "libyml"
|
||||||
|
@ -1897,7 +1927,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2010,9 +2040,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.8.0"
|
version = "0.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
|
checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler2",
|
"adler2",
|
||||||
]
|
]
|
||||||
|
@ -2075,9 +2105,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.36.5"
|
version = "0.36.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
|
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
@ -2133,7 +2163,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2351,7 +2381,7 @@ dependencies = [
|
||||||
"phf_shared 0.11.2",
|
"phf_shared 0.11.2",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2431,7 +2461,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033"
|
checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2545,7 +2575,7 @@ dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2937,9 +2967,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework-sys"
|
name = "security-framework-sys"
|
||||||
version = "2.12.1"
|
version = "2.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2"
|
checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -2988,14 +3018,14 @@ checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.133"
|
version = "1.0.134"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
|
checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa 1.0.14",
|
"itoa 1.0.14",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -3174,9 +3204,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.90"
|
version = "2.0.91"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
|
checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -3200,7 +3230,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3299,7 +3329,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3345,9 +3375,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.8.0"
|
version = "1.8.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
|
checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tinyvec_macros",
|
"tinyvec_macros",
|
||||||
]
|
]
|
||||||
|
@ -3384,7 +3414,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3654,7 +3684,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3689,7 +3719,7 @@ checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
@ -3915,7 +3945,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3937,7 +3967,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3957,7 +3987,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3986,5 +4016,5 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.90",
|
"syn 2.0.91",
|
||||||
]
|
]
|
||||||
|
|
|
@ -21,7 +21,7 @@ serde_json = "1.0"
|
||||||
minify-html = "0.15"
|
minify-html = "0.15"
|
||||||
minify-js = "0.6"
|
minify-js = "0.6"
|
||||||
glob = "0.3"
|
glob = "0.3"
|
||||||
comrak = "0.31"
|
comrak = "0.32"
|
||||||
reqwest = { version = "0.12", features = ["json"] }
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
chrono = { version = "0.4.39", default-features = false, features = ["clock"]}
|
chrono = { version = "0.4.39", default-features = false, features = ["clock"]}
|
||||||
chrono-tz = "0.10"
|
chrono-tz = "0.10"
|
||||||
|
@ -33,6 +33,7 @@ urlencoding = "2.1"
|
||||||
regex = "1.11"
|
regex = "1.11"
|
||||||
cyborgtime = "2.1.1"
|
cyborgtime = "2.1.1"
|
||||||
walkdir = "2.5"
|
walkdir = "2.5"
|
||||||
|
env_logger = "0.11"
|
||||||
|
|
||||||
[lints.clippy]
|
[lints.clippy]
|
||||||
pedantic = "warn"
|
pedantic = "warn"
|
||||||
|
|
|
@ -145,6 +145,8 @@ Markdown file
|
||||||
|
|
||||||
Markdown file is stored in `/app/data/index.md`
|
Markdown file is stored in `/app/data/index.md`
|
||||||
|
|
||||||
|
> For french clients, `/app/data/index-fr.md` will be read instead.
|
||||||
|
|
||||||
```
|
```
|
||||||
---
|
---
|
||||||
name: Option<String>
|
name: Option<String>
|
||||||
|
@ -188,6 +190,8 @@ Post content
|
||||||
|
|
||||||
The file is stored at `/app/data/blog/about.md`.
|
The file is stored at `/app/data/blog/about.md`.
|
||||||
|
|
||||||
|
> For french clients, `/app/data/blog/about-fr.md` will be read instead.
|
||||||
|
|
||||||
## Projects
|
## Projects
|
||||||
|
|
||||||
Markdown files are stored in `/app/data/projects/apps/`
|
Markdown files are stored in `/app/data/projects/apps/`
|
||||||
|
@ -214,6 +218,8 @@ files in `archive` subdirectory of `apps`.
|
||||||
|
|
||||||
The file is stored at `/app/data/projects/about.md`.
|
The file is stored at `/app/data/projects/about.md`.
|
||||||
|
|
||||||
|
> For french clients, `/app/data/projects/about-fr.md` will be read instead.
|
||||||
|
|
||||||
## Contacts
|
## Contacts
|
||||||
|
|
||||||
Markdown files are stored in `/app/data/contacts/`
|
Markdown files are stored in `/app/data/contacts/`
|
||||||
|
@ -254,6 +260,8 @@ For example, `socials` contact files are stored in `/app/data/contacts/socials/`
|
||||||
|
|
||||||
The file is stored at `/app/data/contacts/about.md`.
|
The file is stored at `/app/data/contacts/about.md`.
|
||||||
|
|
||||||
|
> For french clients, `/app/data/contacts/about-fr.md` will be read instead.
|
||||||
|
|
||||||
## Courses
|
## Courses
|
||||||
|
|
||||||
Markdown files are stored in `/app/data/cours/`
|
Markdown files are stored in `/app/data/cours/`
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::{fs::File, io::Write, path::Path};
|
||||||
use crate::template::Template;
|
use crate::template::Template;
|
||||||
|
|
||||||
/// Store the configuration of config/config.toml
|
/// Store the configuration of config/config.toml
|
||||||
#[derive(Clone, Debug, Default, Deserialize)]
|
#[derive(Clone, Debug, Default, Deserialize, Hash, PartialEq, Eq)]
|
||||||
pub struct FileConfiguration {
|
pub struct FileConfiguration {
|
||||||
/// http/https
|
/// http/https
|
||||||
pub scheme: Option<String>,
|
pub scheme: Option<String>,
|
||||||
|
@ -75,14 +75,14 @@ impl FileConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paths where files are stored
|
// Paths where files are stored
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub struct Locations {
|
pub struct Locations {
|
||||||
pub static_dir: String,
|
pub static_dir: String,
|
||||||
pub data_dir: String,
|
pub data_dir: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration used internally in the app
|
/// Configuration used internally in the app
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
/// Information given in the config file
|
/// Information given in the config file
|
||||||
pub fc: FileConfiguration,
|
pub fc: FileConfiguration,
|
||||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -1,6 +1,6 @@
|
||||||
use actix_files::Files;
|
use actix_files::Files;
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
middleware::{Compress, DefaultHeaders},
|
middleware::{Compress, DefaultHeaders, Logger},
|
||||||
web, App, HttpServer,
|
web, App, HttpServer,
|
||||||
};
|
};
|
||||||
use std::io::Result;
|
use std::io::Result;
|
||||||
|
@ -29,16 +29,12 @@ async fn main() -> Result<()> {
|
||||||
config.fc.port.unwrap(),
|
config.fc.port.unwrap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
println!(
|
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||||
"Listening to {}://{}:{}",
|
|
||||||
config.clone().fc.scheme.unwrap(),
|
|
||||||
addr.0,
|
|
||||||
addr.1
|
|
||||||
);
|
|
||||||
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
App::new()
|
App::new()
|
||||||
.app_data(web::Data::new(config.clone()))
|
.app_data(web::Data::new(config.clone()))
|
||||||
|
.wrap(Logger::default().log_target(config.fc.app_name.clone().unwrap_or_default()))
|
||||||
.wrap(Compress::default())
|
.wrap(Compress::default())
|
||||||
.wrap(
|
.wrap(
|
||||||
DefaultHeaders::new()
|
DefaultHeaders::new()
|
||||||
|
|
|
@ -29,6 +29,7 @@ fn build_securitytxt(config: Config) -> String {
|
||||||
pref_lang: config.fc.lang.unwrap_or_default(),
|
pref_lang: config.fc.lang.unwrap_or_default(),
|
||||||
},
|
},
|
||||||
InfosPage::default(),
|
InfosPage::default(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +57,7 @@ fn build_humanstxt(config: Config) -> String {
|
||||||
name: config.fc.fullname.unwrap_or_default(),
|
name: config.fc.fullname.unwrap_or_default(),
|
||||||
},
|
},
|
||||||
InfosPage::default(),
|
InfosPage::default(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,5 +97,6 @@ fn build_webmanifest(config: Config) -> String {
|
||||||
url: get_url(config.fc),
|
url: get_url(config.fc),
|
||||||
},
|
},
|
||||||
InfosPage::default(),
|
InfosPage::default(),
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use actix_web::{get, http::header::ContentType, routes, web, HttpResponse, Responder};
|
use actix_web::{
|
||||||
use cached::proc_macro::once;
|
get, http::header::ContentType, routes, web, HttpRequest, HttpResponse, Responder,
|
||||||
|
};
|
||||||
|
use cached::proc_macro::cached;
|
||||||
use ramhorns::Content;
|
use ramhorns::Content;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -8,14 +10,17 @@ use crate::{
|
||||||
utils::{
|
utils::{
|
||||||
markdown::{File, FilePath},
|
markdown::{File, FilePath},
|
||||||
metadata::MType,
|
metadata::MType,
|
||||||
misc::{make_kw, read_file, Html},
|
misc::{lang, make_kw, read_file_fallback, Html, Lang},
|
||||||
routes::blog::{build_rss, get_post, get_posts, Post, BLOG_DIR, MIME_TYPE_RSS, POST_DIR},
|
routes::blog::{build_rss, get_post, get_posts, Post, BLOG_DIR, MIME_TYPE_RSS, POST_DIR},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[get("/blog")]
|
#[get("/blog")]
|
||||||
pub async fn index(config: web::Data<Config>) -> impl Responder {
|
pub async fn index(req: HttpRequest, config: web::Data<Config>) -> impl Responder {
|
||||||
Html(build_index(config.get_ref().to_owned()))
|
Html(build_index(
|
||||||
|
config.get_ref().to_owned(),
|
||||||
|
lang(req.headers()),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Content, Debug)]
|
#[derive(Content, Debug)]
|
||||||
|
@ -26,18 +31,19 @@ struct BlogIndexTemplate {
|
||||||
no_posts: bool,
|
no_posts: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[once(time = 60)]
|
#[cached(time = 60)]
|
||||||
fn build_index(config: Config) -> String {
|
fn build_index(config: Config, lang: Lang) -> String {
|
||||||
let blog_dir = format!("{}/{}", config.locations.data_dir, BLOG_DIR);
|
let blog_dir = format!("{}/{}", config.locations.data_dir, BLOG_DIR);
|
||||||
let mut posts = get_posts(&format!("{blog_dir}/{POST_DIR}"));
|
let mut posts = get_posts(&format!("{blog_dir}/{POST_DIR}"));
|
||||||
|
|
||||||
// Get about
|
// Get about
|
||||||
let about: Option<File> = read_file(
|
let (about, html_lang) = read_file_fallback(
|
||||||
FilePath {
|
FilePath {
|
||||||
base: blog_dir,
|
base: blog_dir,
|
||||||
path: "about.md".to_owned(),
|
path: "about.md".to_owned(),
|
||||||
},
|
},
|
||||||
MType::Generic,
|
MType::Generic,
|
||||||
|
&lang,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Sort from newest to oldest
|
// Sort from newest to oldest
|
||||||
|
@ -63,6 +69,7 @@ fn build_index(config: Config) -> String {
|
||||||
)),
|
)),
|
||||||
kw: Some(make_kw(&["blog", "blogging"])),
|
kw: Some(make_kw(&["blog", "blogging"])),
|
||||||
},
|
},
|
||||||
|
Some(html_lang),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +108,7 @@ fn build_post(file: &str, config: Config) -> String {
|
||||||
toc,
|
toc,
|
||||||
},
|
},
|
||||||
infos,
|
infos,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use actix_web::{get, routes, web, HttpRequest, Responder};
|
use actix_web::{get, routes, web, HttpRequest, Responder};
|
||||||
use cached::proc_macro::once;
|
use cached::proc_macro::cached;
|
||||||
use ramhorns::Content;
|
use ramhorns::Content;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -8,7 +8,7 @@ use crate::{
|
||||||
utils::{
|
utils::{
|
||||||
markdown::{File, FilePath},
|
markdown::{File, FilePath},
|
||||||
metadata::MType,
|
metadata::MType,
|
||||||
misc::{make_kw, read_file, Html},
|
misc::{lang, make_kw, read_file_fallback, Html, Lang},
|
||||||
routes::contact::{find_links, read, remove_paragraphs},
|
routes::contact::{find_links, read, remove_paragraphs},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -28,8 +28,8 @@ pub fn pages(cfg: &mut web::ServiceConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("")]
|
#[get("")]
|
||||||
async fn page(config: web::Data<Config>) -> impl Responder {
|
async fn page(req: HttpRequest, config: web::Data<Config>) -> impl Responder {
|
||||||
Html(build_page(config.get_ref().to_owned()))
|
Html(build_page(config.get_ref().to_owned(), lang(req.headers())))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[routes]
|
#[routes]
|
||||||
|
@ -78,18 +78,19 @@ struct NetworksTemplate {
|
||||||
others: Vec<File>,
|
others: Vec<File>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[once(time = 60)]
|
#[cached(time = 60)]
|
||||||
fn build_page(config: Config) -> String {
|
fn build_page(config: Config, lang: Lang) -> String {
|
||||||
let contacts_dir = format!("{}/{}", config.locations.data_dir, CONTACT_DIR);
|
let contacts_dir = format!("{}/{}", config.locations.data_dir, CONTACT_DIR);
|
||||||
let ext = ".md";
|
let ext = ".md";
|
||||||
|
|
||||||
// Get about
|
// Get about
|
||||||
let about = read_file(
|
let (about, html_lang) = read_file_fallback(
|
||||||
FilePath {
|
FilePath {
|
||||||
base: contacts_dir.clone(),
|
base: contacts_dir.clone(),
|
||||||
path: "about.md".to_owned(),
|
path: "about.md".to_owned(),
|
||||||
},
|
},
|
||||||
MType::Generic,
|
MType::Generic,
|
||||||
|
&lang,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut socials = read(&FilePath {
|
let mut socials = read(&FilePath {
|
||||||
|
@ -138,5 +139,6 @@ fn build_page(config: Config) -> String {
|
||||||
"linktree",
|
"linktree",
|
||||||
])),
|
])),
|
||||||
},
|
},
|
||||||
|
Some(html_lang),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,5 +87,6 @@ async fn build_page(config: Config) -> String {
|
||||||
"code",
|
"code",
|
||||||
])),
|
])),
|
||||||
},
|
},
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,13 +60,14 @@ fn get_content(
|
||||||
path: filename.to_owned(),
|
path: filename.to_owned(),
|
||||||
},
|
},
|
||||||
MType::Cours,
|
MType::Cours,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_page(info: &web::Query<PathRequest>, config: Config) -> String {
|
fn build_page(info: &web::Query<PathRequest>, config: Config) -> String {
|
||||||
let cours_dir = "data/cours";
|
let cours_dir = "data/cours";
|
||||||
|
|
||||||
let (ep, el): (_, Vec<String>) = config
|
let (ep, el): (_, Vec<_>) = config
|
||||||
.fc
|
.fc
|
||||||
.exclude_courses
|
.exclude_courses
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -106,5 +107,6 @@ fn build_page(info: &web::Query<PathRequest>, config: Config) -> String {
|
||||||
"digital garden",
|
"digital garden",
|
||||||
])),
|
])),
|
||||||
},
|
},
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use actix_web::{get, web, Responder};
|
use actix_web::{get, web, HttpRequest, Responder};
|
||||||
use cached::proc_macro::once;
|
use cached::proc_macro::cached;
|
||||||
use ramhorns::Content;
|
use ramhorns::Content;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -8,13 +8,13 @@ use crate::{
|
||||||
utils::{
|
utils::{
|
||||||
markdown::{File, FilePath},
|
markdown::{File, FilePath},
|
||||||
metadata::MType,
|
metadata::MType,
|
||||||
misc::{make_kw, read_file, Html},
|
misc::{lang, make_kw, read_file, read_file_fallback, Html, Lang},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
pub async fn page(config: web::Data<Config>) -> impl Responder {
|
pub async fn page(req: HttpRequest, config: web::Data<Config>) -> impl Responder {
|
||||||
Html(build_page(config.get_ref().to_owned()))
|
Html(build_page(config.get_ref().to_owned(), lang(req.headers())))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Content, Debug)]
|
#[derive(Content, Debug)]
|
||||||
|
@ -34,14 +34,15 @@ struct StyleAvatar {
|
||||||
square: bool,
|
square: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[once(time = 60)]
|
#[cached(time = 60)]
|
||||||
fn build_page(config: Config) -> String {
|
fn build_page(config: Config, lang: Lang) -> String {
|
||||||
let mut file = read_file(
|
let (mut file, html_lang) = read_file_fallback(
|
||||||
FilePath {
|
FilePath {
|
||||||
base: config.locations.data_dir,
|
base: config.locations.data_dir.clone(),
|
||||||
path: "index.md".to_owned(),
|
path: "index.md".to_owned(),
|
||||||
},
|
},
|
||||||
MType::Index,
|
MType::Index,
|
||||||
|
&lang,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Default values
|
// Default values
|
||||||
|
@ -77,6 +78,7 @@ fn build_page(config: Config) -> String {
|
||||||
path: "README.md".to_owned(),
|
path: "README.md".to_owned(),
|
||||||
},
|
},
|
||||||
MType::Generic,
|
MType::Generic,
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,5 +101,6 @@ fn build_page(config: Config) -> String {
|
||||||
desc: Some("Page principale".into()),
|
desc: Some("Page principale".into()),
|
||||||
kw: Some(make_kw(&["index", "étudiant", "accueil"])),
|
kw: Some(make_kw(&["index", "étudiant", "accueil"])),
|
||||||
},
|
},
|
||||||
|
Some(html_lang),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,5 +32,6 @@ fn build_page(config: Config) -> String {
|
||||||
desc: Some("Une page perdu du web".into()),
|
desc: Some("Une page perdu du web".into()),
|
||||||
..InfosPage::default()
|
..InfosPage::default()
|
||||||
},
|
},
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use actix_web::{get, web, Responder};
|
use actix_web::{get, web, HttpRequest, Responder};
|
||||||
use cached::proc_macro::once;
|
use cached::proc_macro::cached;
|
||||||
use glob::glob;
|
use glob::glob;
|
||||||
use ramhorns::Content;
|
use ramhorns::Content;
|
||||||
|
|
||||||
|
@ -9,13 +9,13 @@ use crate::{
|
||||||
utils::{
|
utils::{
|
||||||
markdown::{File, FilePath},
|
markdown::{File, FilePath},
|
||||||
metadata::MType,
|
metadata::MType,
|
||||||
misc::{make_kw, read_file, Html},
|
misc::{lang, make_kw, read_file, read_file_fallback, Html, Lang},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[get("/portfolio")]
|
#[get("/portfolio")]
|
||||||
pub async fn page(config: web::Data<Config>) -> impl Responder {
|
pub async fn page(req: HttpRequest, config: web::Data<Config>) -> impl Responder {
|
||||||
Html(build_page(config.get_ref().to_owned()))
|
Html(build_page(config.get_ref().to_owned(), lang(req.headers())))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Content, Debug)]
|
#[derive(Content, Debug)]
|
||||||
|
@ -29,8 +29,8 @@ struct PortfolioTemplate<'a> {
|
||||||
err_msg: &'a str,
|
err_msg: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[once(time = 60)]
|
#[cached(time = 60)]
|
||||||
fn build_page(config: Config) -> String {
|
fn build_page(config: Config, lang: Lang) -> String {
|
||||||
let projects_dir = format!("{}/projects", config.locations.data_dir);
|
let projects_dir = format!("{}/projects", config.locations.data_dir);
|
||||||
let apps_dir = FilePath {
|
let apps_dir = FilePath {
|
||||||
base: format!("{projects_dir}/apps"),
|
base: format!("{projects_dir}/apps"),
|
||||||
|
@ -39,12 +39,13 @@ fn build_page(config: Config) -> String {
|
||||||
let ext = ".md";
|
let ext = ".md";
|
||||||
|
|
||||||
// Get about
|
// Get about
|
||||||
let about = read_file(
|
let (about, html_lang) = read_file_fallback(
|
||||||
FilePath {
|
FilePath {
|
||||||
base: projects_dir,
|
base: projects_dir,
|
||||||
path: "about.md".to_owned(),
|
path: "about.md".to_owned(),
|
||||||
},
|
},
|
||||||
MType::Generic,
|
MType::Generic,
|
||||||
|
&lang,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Get apps
|
// Get apps
|
||||||
|
@ -54,6 +55,7 @@ fn build_page(config: Config) -> String {
|
||||||
read_file(
|
read_file(
|
||||||
apps_dir.from(&e.unwrap().to_string_lossy()),
|
apps_dir.from(&e.unwrap().to_string_lossy()),
|
||||||
MType::Portfolio,
|
MType::Portfolio,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
})
|
})
|
||||||
|
@ -72,6 +74,7 @@ fn build_page(config: Config) -> String {
|
||||||
read_file(
|
read_file(
|
||||||
apps_dir.from(&e.unwrap().to_string_lossy()),
|
apps_dir.from(&e.unwrap().to_string_lossy()),
|
||||||
MType::Portfolio,
|
MType::Portfolio,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
})
|
})
|
||||||
|
@ -112,5 +115,6 @@ fn build_page(config: Config) -> String {
|
||||||
"code",
|
"code",
|
||||||
])),
|
])),
|
||||||
},
|
},
|
||||||
|
Some(html_lang),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,5 +22,6 @@ fn build_page(config: Config) -> String {
|
||||||
desc: Some("Coin reculé de l'internet".into()),
|
desc: Some("Coin reculé de l'internet".into()),
|
||||||
kw: Some(make_kw(&["web3", "blockchain", "nft", "ai"])),
|
kw: Some(make_kw(&["web3", "blockchain", "nft", "ai"])),
|
||||||
},
|
},
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
use ramhorns::{Content, Ramhorns};
|
use ramhorns::{Content, Ramhorns};
|
||||||
|
|
||||||
|
use crate::utils::misc::Lang;
|
||||||
|
|
||||||
/// Structure used in the config variable of the app
|
/// Structure used in the config variable of the app
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
pub struct Template {
|
pub struct Template {
|
||||||
/// Root directory where templates are stored
|
/// Root directory where templates are stored
|
||||||
pub directory: String,
|
pub directory: String,
|
||||||
|
@ -51,12 +53,20 @@ struct DataPage<T> {
|
||||||
page_kw: Option<String>,
|
page_kw: Option<String>,
|
||||||
/// Author's name
|
/// Author's name
|
||||||
page_author: Option<String>,
|
page_author: Option<String>,
|
||||||
|
/// Language used
|
||||||
|
lang: String,
|
||||||
/// Data needed to render the page
|
/// Data needed to render the page
|
||||||
data: T,
|
data: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Template {
|
impl Template {
|
||||||
pub fn render<C: Content>(&self, template: &str, data: C, info: InfosPage) -> String {
|
pub fn render<C: Content>(
|
||||||
|
&self,
|
||||||
|
template: &str,
|
||||||
|
data: C,
|
||||||
|
info: InfosPage,
|
||||||
|
lang: Option<String>,
|
||||||
|
) -> String {
|
||||||
let mut templates: Ramhorns = Ramhorns::lazy(&self.directory).unwrap();
|
let mut templates: Ramhorns = Ramhorns::lazy(&self.directory).unwrap();
|
||||||
let tplt = templates.from_file(template).unwrap();
|
let tplt = templates.from_file(template).unwrap();
|
||||||
|
|
||||||
|
@ -67,6 +77,7 @@ impl Template {
|
||||||
page_desc: info.desc,
|
page_desc: info.desc,
|
||||||
page_kw: info.kw,
|
page_kw: info.kw,
|
||||||
page_author: self.name.clone(),
|
page_author: self.name.clone(),
|
||||||
|
lang: lang.unwrap_or(Lang::default()),
|
||||||
data,
|
data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ use comrak::nodes::{AstNode, NodeValue};
|
||||||
use comrak::{format_html, parse_document, Arena, ComrakOptions, ListStyleType, Options};
|
use comrak::{format_html, parse_document, Arena, ComrakOptions, ListStyleType, Options};
|
||||||
use lol_html::html_content::ContentType;
|
use lol_html::html_content::ContentType;
|
||||||
use lol_html::{element, rewrite_str, HtmlRewriter, RewriteStrSettings, Settings};
|
use lol_html::{element, rewrite_str, HtmlRewriter, RewriteStrSettings, Settings};
|
||||||
use mime_guess::mime;
|
|
||||||
use ramhorns::Content;
|
use ramhorns::Content;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
@ -52,7 +51,7 @@ impl FilePath {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Options used for parser and compiler MD --> HTML
|
/// Options used for parser and compiler MD --> HTML
|
||||||
pub fn get_options(path: Option<FilePath>, metadata_type: MType) -> ComrakOptions {
|
pub fn get_options(path: Option<FilePath>, metadata_type: MType) -> ComrakOptions<'static> {
|
||||||
comrak::Options {
|
comrak::Options {
|
||||||
extension: comrak::ExtensionOptions::builder()
|
extension: comrak::ExtensionOptions::builder()
|
||||||
.strikethrough(true)
|
.strikethrough(true)
|
||||||
|
@ -163,15 +162,10 @@ fn custom_img_size(html: &str) -> String {
|
||||||
rewrite_str(
|
rewrite_str(
|
||||||
html,
|
html,
|
||||||
RewriteStrSettings {
|
RewriteStrSettings {
|
||||||
element_content_handlers: vec![element!("img[alt], svg", |el| {
|
element_content_handlers: vec![element!("img[alt]", |el| {
|
||||||
let alt = el.get_attribute("alt").unwrap_or_default();
|
let alt = el.get_attribute("alt").unwrap();
|
||||||
let possible_piece = alt.split('|').collect::<Vec<&str>>();
|
let possible_piece = alt.split('|').collect::<Vec<&str>>();
|
||||||
|
|
||||||
if el.tag_name() == *"svg" {
|
|
||||||
el.remove_attribute("width");
|
|
||||||
el.remove_attribute("height");
|
|
||||||
}
|
|
||||||
|
|
||||||
if possible_piece.len() > 1 {
|
if possible_piece.len() > 1 {
|
||||||
let data = possible_piece.last().unwrap().trim();
|
let data = possible_piece.last().unwrap().trim();
|
||||||
|
|
||||||
|
@ -261,7 +255,7 @@ fn fix_images_and_integration(
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
if let Ok(mut file_contents) = fs::read(&img_path) {
|
if let Ok(file_contents) = fs::read(&img_path) {
|
||||||
let mime = mime_guess::from_path(&img_path).first_or_octet_stream();
|
let mime = mime_guess::from_path(&img_path).first_or_octet_stream();
|
||||||
if recursive && mime == "text/markdown" {
|
if recursive && mime == "text/markdown" {
|
||||||
let file_str = String::from_utf8_lossy(&file_contents).into_owned();
|
let file_str = String::from_utf8_lossy(&file_contents).into_owned();
|
||||||
|
@ -292,16 +286,6 @@ fn fix_images_and_integration(
|
||||||
|
|
||||||
// Store the metadata for later merging
|
// Store the metadata for later merging
|
||||||
additional_metadata.push(data.metadata);
|
additional_metadata.push(data.metadata);
|
||||||
} else if mime == mime::IMAGE_SVG {
|
|
||||||
let alt = el.get_attribute("alt").unwrap_or_default();
|
|
||||||
file_contents.drain(0..3);
|
|
||||||
el.replace(
|
|
||||||
&format!(
|
|
||||||
"<svg alt='{alt}'{}",
|
|
||||||
String::from_utf8(file_contents).unwrap()
|
|
||||||
),
|
|
||||||
ContentType::Html,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
let image = general_purpose::STANDARD.encode(&file_contents);
|
let image = general_purpose::STANDARD.encode(&file_contents);
|
||||||
el.set_attribute("src", &format!("data:{mime};base64,{image}"))
|
el.set_attribute("src", &format!("data:{mime};base64,{image}"))
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
use std::{fs, path::Path};
|
use std::{fs, os::unix::fs::MetadataExt, path::Path};
|
||||||
|
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
http::header::{self, ContentType, TryIntoHeaderValue},
|
http::{
|
||||||
http::StatusCode,
|
header::{self, ContentType, HeaderMap, TryIntoHeaderValue},
|
||||||
|
StatusCode,
|
||||||
|
},
|
||||||
HttpRequest, HttpResponse, Responder,
|
HttpRequest, HttpResponse, Responder,
|
||||||
};
|
};
|
||||||
use base64::{engine::general_purpose, Engine};
|
use base64::{engine::general_purpose, Engine};
|
||||||
use cached::proc_macro::cached;
|
use cached::proc_macro::cached;
|
||||||
|
use mime_guess::mime;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
|
|
||||||
use crate::config::FileConfiguration;
|
use crate::config::FileConfiguration;
|
||||||
|
@ -55,22 +58,60 @@ impl Responder for Html {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read a file localized, fallback to default file if localized file isn't found
|
||||||
|
pub fn read_file_fallback(
|
||||||
|
filename: FilePath,
|
||||||
|
expected_file: MType,
|
||||||
|
lang: &Lang,
|
||||||
|
) -> (Option<File>, String) {
|
||||||
|
match read_file(filename.clone(), expected_file, Some(lang.clone())) {
|
||||||
|
None => (
|
||||||
|
read_file(filename, expected_file, None),
|
||||||
|
Lang::English.to_string(),
|
||||||
|
),
|
||||||
|
data => (data, lang.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Read a file
|
/// Read a file
|
||||||
pub fn read_file(filename: FilePath, expected_file: MType) -> Option<File> {
|
pub fn read_file(filename: FilePath, expected_file: MType, lang: Option<Lang>) -> Option<File> {
|
||||||
reader(filename, expected_file)
|
reader(filename, expected_file, lang.unwrap_or(Lang::English))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cached(time = 600)]
|
#[cached(time = 600)]
|
||||||
fn reader(filename: FilePath, expected_file: MType) -> Option<File> {
|
fn reader(filename: FilePath, expected_file: MType, lang: Lang) -> Option<File> {
|
||||||
let as_str = filename.to_string();
|
let as_str = match lang {
|
||||||
Path::new(&as_str)
|
Lang::French => {
|
||||||
.extension()
|
let str = filename.to_string();
|
||||||
.and_then(|ext| match ext.to_str().unwrap() {
|
let mut parts = str.split('.').collect::<Vec<_>>();
|
||||||
"pdf" => fs::read(&as_str).map_or(None, |bytes| Some(read_pdf(bytes))),
|
let extension = parts.pop().unwrap_or("");
|
||||||
_ => fs::read_to_string(&as_str).map_or(None, |text| {
|
let filename = parts.join(".");
|
||||||
|
&format!("{filename}-fr.{extension}")
|
||||||
|
}
|
||||||
|
Lang::English => &filename.to_string(),
|
||||||
|
};
|
||||||
|
let path = Path::new(as_str);
|
||||||
|
|
||||||
|
if let Ok(metadata) = path.metadata() {
|
||||||
|
// Taille maximale : 30M
|
||||||
|
if metadata.size() > 30 * 1000 * 1000 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path.extension().and_then(|ext| {
|
||||||
|
match mime_guess::from_ext(ext.to_str().unwrap_or_default()).first_or_text_plain() {
|
||||||
|
mime if mime == mime::APPLICATION_PDF => {
|
||||||
|
fs::read(as_str).map_or(None, |bytes| Some(read_pdf(bytes)))
|
||||||
|
}
|
||||||
|
mime if mime.type_() == mime::IMAGE => {
|
||||||
|
fs::read(as_str).map_or(None, |bytes| Some(read_img(bytes, &mime)))
|
||||||
|
}
|
||||||
|
_ => fs::read_to_string(as_str).map_or(None, |text| {
|
||||||
Some(read_md(&filename, &text, expected_file, None, true))
|
Some(read_md(&filename, &text, expected_file, None, true))
|
||||||
}),
|
}),
|
||||||
})
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_pdf(data: Vec<u8>) -> File {
|
fn read_pdf(data: Vec<u8>) -> File {
|
||||||
|
@ -80,14 +121,59 @@ fn read_pdf(data: Vec<u8>) -> File {
|
||||||
metadata: Metadata::default(),
|
metadata: Metadata::default(),
|
||||||
content: format!(
|
content: format!(
|
||||||
r#"<embed
|
r#"<embed
|
||||||
src="data:application/pdf;base64,{pdf}"
|
src="data:{};base64,{pdf}"
|
||||||
style="width: 100%; height: 79vh";
|
style="width: 100%; height: 79vh";
|
||||||
>"#
|
>"#,
|
||||||
|
mime::APPLICATION_PDF
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_img(data: Vec<u8>, mime: &mime::Mime) -> File {
|
||||||
|
let image = general_purpose::STANDARD.encode(data);
|
||||||
|
|
||||||
|
File {
|
||||||
|
metadata: Metadata::default(),
|
||||||
|
content: format!("<img src='data:{mime};base64,{image}'>"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Remove the first character of a string
|
/// Remove the first character of a string
|
||||||
pub fn remove_first_letter(s: &str) -> &str {
|
pub fn remove_first_letter(s: &str) -> &str {
|
||||||
s.chars().next().map(|c| &s[c.len_utf8()..]).unwrap()
|
s.chars().next().map(|c| &s[c.len_utf8()..]).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Hash, PartialEq, Eq, Clone)]
|
||||||
|
pub enum Lang {
|
||||||
|
French,
|
||||||
|
English,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Lang {
|
||||||
|
pub fn default() -> String {
|
||||||
|
Lang::French.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Lang {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Lang::French => write!(f, "fr"),
|
||||||
|
Lang::English => write!(f, "en"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the browser language
|
||||||
|
pub fn lang(headers: &HeaderMap) -> Lang {
|
||||||
|
headers
|
||||||
|
.get("Accept-Language")
|
||||||
|
.and_then(|lang| lang.to_str().ok())
|
||||||
|
.and_then(|lang| {
|
||||||
|
["fr", "fr-FR"]
|
||||||
|
.into_iter()
|
||||||
|
.any(|i| lang.contains(i))
|
||||||
|
.then_some(Lang::French)
|
||||||
|
})
|
||||||
|
.unwrap_or(Lang::English)
|
||||||
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ impl Post {
|
||||||
path: format!("{}{ext}", self.url),
|
path: format!("{}{ext}", self.url),
|
||||||
},
|
},
|
||||||
MType::Blog,
|
MType::Blog,
|
||||||
|
None,
|
||||||
) {
|
) {
|
||||||
self.content = Some(file.content);
|
self.content = Some(file.content);
|
||||||
}
|
}
|
||||||
|
@ -149,6 +150,7 @@ pub fn get_post(
|
||||||
path: format!("{filename}{ext}"),
|
path: format!("{filename}{ext}"),
|
||||||
},
|
},
|
||||||
MType::Blog,
|
MType::Blog,
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
let default = (
|
let default = (
|
||||||
|
|
|
@ -57,7 +57,14 @@ pub fn remove_paragraphs(list: &mut [File]) {
|
||||||
pub fn read(path: &FilePath) -> Vec<File> {
|
pub fn read(path: &FilePath) -> Vec<File> {
|
||||||
glob(&path.to_string())
|
glob(&path.to_string())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.map(|e| read_file(path.from(&e.unwrap().to_string_lossy()), MType::Contact).unwrap())
|
.map(|e| {
|
||||||
|
read_file(
|
||||||
|
path.from(&e.unwrap().to_string_lossy()),
|
||||||
|
MType::Contact,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
.filter(|f| {
|
.filter(|f| {
|
||||||
!f.metadata
|
!f.metadata
|
||||||
.info
|
.info
|
||||||
|
|
|
@ -58,8 +58,7 @@ main {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Images */
|
/* Images */
|
||||||
img,
|
img {
|
||||||
svg {
|
|
||||||
max-width: var(--max-width);
|
max-width: var(--max-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,8 +70,7 @@ aside a {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Image */
|
/* Image */
|
||||||
main img,
|
main img {
|
||||||
main svg {
|
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,7 @@ main a.anchor {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Images */
|
/* Images */
|
||||||
main img,
|
main img {
|
||||||
main svg {
|
|
||||||
display: block;
|
display: block;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
@ -250,7 +249,6 @@ table:not(.hljs-ln) tr th:last-child {
|
||||||
|
|
||||||
/* Prevent figures from splitting accross pages */
|
/* Prevent figures from splitting accross pages */
|
||||||
article *:has(img),
|
article *:has(img),
|
||||||
article *:has(svg),
|
|
||||||
table:not(.hljs-ln),
|
table:not(.hljs-ln),
|
||||||
table:not(.hljs-ln) > * {
|
table:not(.hljs-ln) > * {
|
||||||
page-break-inside: avoid;
|
page-break-inside: avoid;
|
||||||
|
|
|
@ -83,7 +83,7 @@ header nav a:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add links */
|
/* Add links */
|
||||||
a:not(:where([href^="#"], [href^="/"])):not(:has(img)):not(:has(svg))::after {
|
a:not(:where([href^="#"], [href^="/"])):not(:has(img))::after {
|
||||||
content: " (" attr(href) ")";
|
content: " (" attr(href) ")";
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
|
@ -96,8 +96,7 @@ header nav a:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
img,
|
img {
|
||||||
svg {
|
|
||||||
filter: brightness(0.8) contrast(1.2);
|
filter: brightness(0.8) contrast(1.2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,56 +4,36 @@ const Mode = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert to grayscale
|
* Change the svg color theme based on the mode
|
||||||
* @param {{r: number, g: number, b: number}} color
|
* @param {Mode} mode
|
||||||
* @returns Number between 0 and 255
|
|
||||||
*/
|
*/
|
||||||
const colorToGrayscale = (color) => {
|
const svgChangeTheme = (mode) => {
|
||||||
return 0.3 * color.r + 0.59 * color.g + 0.11 * color.b;
|
for (const item of document.getElementsByTagName("img")) {
|
||||||
};
|
if (!item.src.startsWith("data:image/svg+xml;base64,")) {
|
||||||
|
// Exclude image who aren't SVG and base64 encoded
|
||||||
/**
|
continue;
|
||||||
* Extract colors from an image or SVG element using canvas
|
|
||||||
* @param {HTMLImageElement | SVGSVGElement} element Image or SVG source element
|
|
||||||
* @returns {Promise<Array>} Colors representing the element
|
|
||||||
*/
|
|
||||||
const extractColors = (element) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
// Create a canvas with the same dimensions as the element
|
|
||||||
const canvas = document.createElement("canvas");
|
|
||||||
let imageSource;
|
|
||||||
|
|
||||||
if (element instanceof SVGSVGElement) {
|
|
||||||
// SVG element handling
|
|
||||||
canvas.width = element.viewBox.baseVal.width;
|
|
||||||
canvas.height = element.viewBox.baseVal.height;
|
|
||||||
|
|
||||||
const svgString = new XMLSerializer().serializeToString(element);
|
|
||||||
const svgBlob = new Blob([svgString], { type: "image/svg+xml" });
|
|
||||||
const svgUrl = URL.createObjectURL(svgBlob);
|
|
||||||
|
|
||||||
imageSource = new Image();
|
|
||||||
imageSource.src = svgUrl;
|
|
||||||
|
|
||||||
imageSource.onload = () => {
|
|
||||||
processImage(imageSource);
|
|
||||||
URL.revokeObjectURL(svgUrl);
|
|
||||||
};
|
|
||||||
|
|
||||||
imageSource.onerror = (error) => {
|
|
||||||
URL.revokeObjectURL(svgUrl);
|
|
||||||
reject(error);
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// Regular image handling
|
|
||||||
canvas.width = element.naturalWidth || element.width;
|
|
||||||
canvas.height = element.naturalHeight || element.height;
|
|
||||||
processImage(element);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const processImage = (img) => {
|
/**
|
||||||
|
* Convert to grayscale
|
||||||
|
* @param {{r: number, g: number, b: number}} color
|
||||||
|
* @returns Number between 0 and 255
|
||||||
|
*/
|
||||||
|
const colorToGrayscale = (color) => {
|
||||||
|
return 0.3 * color.r + 0.59 * color.g + 0.11 * color.b;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract color using canvas2d
|
||||||
|
* @param {HTMLImageElement} image Image source
|
||||||
|
* @returns Colors represeting the image
|
||||||
|
*/
|
||||||
|
const extractColors = (image) => {
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = image.naturalWidth;
|
||||||
|
canvas.height = image.naturalHeight;
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
ctx.drawImage(img, 0, 0);
|
ctx.drawImage(image, 0, 0);
|
||||||
|
|
||||||
const imageData = ctx.getImageData(
|
const imageData = ctx.getImageData(
|
||||||
0,
|
0,
|
||||||
|
@ -74,83 +54,44 @@ const extractColors = (element) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(colors);
|
return colors;
|
||||||
};
|
};
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
// Extract colors
|
||||||
* Change the color theme based on the mode
|
const colors = extractColors(item);
|
||||||
* @param {Mode} mode
|
|
||||||
*/
|
|
||||||
const changeTheme = (mode) => {
|
|
||||||
// Process SVG elements
|
|
||||||
for (const svgElement of document.getElementsByTagName("svg")) {
|
|
||||||
extractColors(svgElement)
|
|
||||||
.then((colors) => {
|
|
||||||
applyFilter(mode, svgElement, colors);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error("Error extracting colors:", error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process SVG images embedded as base64
|
// Calculate the average grayscale value
|
||||||
for (const item of document.getElementsByTagName("img")) {
|
const grayscaleValues = colors.map(colorToGrayscale);
|
||||||
if (!item.src.startsWith("data:image/svg+xml;base64,")) {
|
const totalGrayscale = grayscaleValues.reduce((acc, val) => acc + val, 0);
|
||||||
// Exclude images that aren't SVG and base64 encoded
|
const averageGrayscale = totalGrayscale / grayscaleValues.length;
|
||||||
|
|
||||||
|
const treshold = 128;
|
||||||
|
|
||||||
|
const style = "filter: ";
|
||||||
|
const dim = "brightness(0.8) contrast(1.2)";
|
||||||
|
|
||||||
|
if (averageGrayscale < treshold && mode === Mode.Dark) {
|
||||||
|
item.style = style + dim + " invert(1);";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
extractColors(item)
|
if (averageGrayscale > treshold && mode === Mode.Light) {
|
||||||
.then((colors) => {
|
item.style = style + "invert(1);";
|
||||||
applyFilter(mode, item, colors);
|
continue;
|
||||||
})
|
}
|
||||||
.catch((error) => {
|
|
||||||
console.error("Error extracting colors:", error);
|
if (mode === Mode.Dark) {
|
||||||
});
|
item.style = style + `${dim};`;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.style = "";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply the filter based on the selected mode
|
|
||||||
* @param {Mode} mode Current theme
|
|
||||||
* @param {HTMLImageElement | SVGSVGElement} item Element
|
|
||||||
* @param {{r: number, g: number, b: number}} colors Array of the colors of the element
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const applyFilter = (mode, item, colors) => {
|
|
||||||
// Calculate the average grayscale value
|
|
||||||
const grayscaleValues = colors.map(colorToGrayscale);
|
|
||||||
const totalGrayscale = grayscaleValues.reduce((acc, val) => acc + val, 0);
|
|
||||||
const averageGrayscale = totalGrayscale / grayscaleValues.length;
|
|
||||||
|
|
||||||
const treshold = 128;
|
|
||||||
|
|
||||||
const style = "filter: ";
|
|
||||||
const dim = "brightness(0.8) contrast(1.2)";
|
|
||||||
|
|
||||||
if (averageGrayscale < treshold && mode === Mode.Dark) {
|
|
||||||
item.style = style + dim + " invert(1);";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (averageGrayscale > treshold && mode === Mode.Light) {
|
|
||||||
item.style = style + "invert(1);";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode === Mode.Dark) {
|
|
||||||
item.style = style + `${dim};`;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
item.style = "";
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("load", () => {
|
window.addEventListener("load", () => {
|
||||||
// Fix SVG images
|
// Fix SVG images
|
||||||
changeTheme(
|
svgChangeTheme(
|
||||||
window.matchMedia("(prefers-color-scheme: dark)").matches
|
window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||||
? Mode.Dark
|
? Mode.Dark
|
||||||
: Mode.Light
|
: Mode.Light
|
||||||
|
@ -160,5 +101,5 @@ window.addEventListener("load", () => {
|
||||||
window
|
window
|
||||||
.matchMedia("(prefers-color-scheme: dark)")
|
.matchMedia("(prefers-color-scheme: dark)")
|
||||||
.addEventListener("change", (event) =>
|
.addEventListener("change", (event) =>
|
||||||
changeTheme(event.matches ? Mode.Dark : Mode.Light)
|
svgChangeTheme(event.matches ? Mode.Dark : Mode.Light)
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="{{lang}}">
|
||||||
<head dir="ltr">
|
<head dir="ltr">
|
||||||
{{>head.html}}
|
{{>head.html}}
|
||||||
</head>
|
</head>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="{{lang}}">
|
||||||
<head dir="ltr">
|
<head dir="ltr">
|
||||||
{{>head.html}}
|
{{>head.html}}
|
||||||
<link
|
<link
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="{{lang}}">
|
||||||
<head dir="ltr">
|
<head dir="ltr">
|
||||||
{{>head.html}}
|
{{>head.html}}
|
||||||
<link
|
<link
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="{{lang}}">
|
||||||
<head dir="ltr">
|
<head dir="ltr">
|
||||||
{{>head.html}}
|
{{>head.html}}
|
||||||
<link rel="stylesheet" href="/css/contact.css" />
|
<link rel="stylesheet" href="/css/contact.css" />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="{{lang}}">
|
||||||
<head dir="ltr">
|
<head dir="ltr">
|
||||||
{{>head.html}}
|
{{>head.html}}
|
||||||
<link rel="stylesheet" href="/css/contrib.css" />
|
<link rel="stylesheet" href="/css/contrib.css" />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="{{lang}}">
|
||||||
<head dir="ltr">
|
<head dir="ltr">
|
||||||
{{>head.html}}
|
{{>head.html}}
|
||||||
<link rel="stylesheet" href="/css/cours.css" />
|
<link rel="stylesheet" href="/css/cours.css" />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="{{lang}}">
|
||||||
<head dir="ltr">
|
<head dir="ltr">
|
||||||
{{>head.html}}
|
{{>head.html}}
|
||||||
<link rel="stylesheet" href="/css/index.css" />
|
<link rel="stylesheet" href="/css/index.css" />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="fr">
|
<html lang="{{lang}}">
|
||||||
<head dir="ltr">
|
<head dir="ltr">
|
||||||
{{>head.html}}
|
{{>head.html}}
|
||||||
<link rel="stylesheet" href="/css/portfolio.css" />
|
<link rel="stylesheet" href="/css/portfolio.css" />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html class="index" lang="fr">
|
<html class="index" lang="{{lang}}">
|
||||||
<head dir="ltr">
|
<head dir="ltr">
|
||||||
<title>{{page_title}}{{#page_title}} - {{/page_title}}{{app_name}}</title>
|
<title>{{page_title}}{{#page_title}} - {{/page_title}}{{app_name}}</title>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
|
Loading…
Reference in a new issue