Compare commits
20 commits
main
...
math-rewor
Author | SHA1 | Date | |
---|---|---|---|
ecbbe85844 | |||
2cb1e664fe | |||
5520952d07 | |||
6eef32b6d9 | |||
7fc3f95dd5 | |||
61015a2536 | |||
2a44f1240f | |||
a738d492d2 | |||
fe9a0c750b | |||
fcc146842c | |||
23079f4418 | |||
a9f48a79a4 | |||
b3cdcff067 | |||
1c19d23f36 | |||
bbd86393eb | |||
9bf1bc807c | |||
d025981f0e | |||
fc44816a04 | |||
4ccd2c8709 | |||
21cb50e5fd |
21 changed files with 385 additions and 47 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -927,9 +927,11 @@ dependencies = [
|
||||||
"minify-html",
|
"minify-html",
|
||||||
"minify-js 0.5.6",
|
"minify-js 0.5.6",
|
||||||
"ramhorns",
|
"ramhorns",
|
||||||
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rss",
|
"rss",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"toml",
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
|
@ -17,6 +17,7 @@ ramhorns = "0.14"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
|
serde_json = "1.0"
|
||||||
minify-html = "0.11"
|
minify-html = "0.11"
|
||||||
minify-js = "0.5"
|
minify-js = "0.5"
|
||||||
glob = "0.3"
|
glob = "0.3"
|
||||||
|
@ -26,3 +27,4 @@ chrono = { version = "0.4.30", default-features = false, features = ["clock"]}
|
||||||
chrono-tz = "0.8"
|
chrono-tz = "0.8"
|
||||||
rss = { version = "2.0", features = ["atom"] }
|
rss = { version = "2.0", features = ["atom"] }
|
||||||
lol_html = "1.2"
|
lol_html = "1.2"
|
||||||
|
regex = "1.10"
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
- [Blog](#blog)
|
- [Blog](#blog)
|
||||||
- [Projects](#projects)
|
- [Projects](#projects)
|
||||||
- [Contacts](#contacts)
|
- [Contacts](#contacts)
|
||||||
|
- [Courses](#courses)
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
|
@ -204,3 +205,7 @@ Custom project description
|
||||||
- `user` is the username used in the platform
|
- `user` is the username used in the platform
|
||||||
- `description` will be rendered as HTML "title" (text will appear when cursor
|
- `description` will be rendered as HTML "title" (text will appear when cursor
|
||||||
is hover the link)
|
is hover the link)
|
||||||
|
|
||||||
|
## Courses
|
||||||
|
|
||||||
|
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(Deserialize, Clone, Default, Debug)]
|
#[derive(Clone, Debug, Default, Deserialize)]
|
||||||
pub struct FileConfig {
|
pub struct FileConfig {
|
||||||
/// http/https
|
/// http/https
|
||||||
pub scheme: Option<String>,
|
pub scheme: Option<String>,
|
||||||
|
|
|
@ -5,12 +5,12 @@ use serde::Deserialize;
|
||||||
|
|
||||||
use crate::misc::utils::get_reqwest_client;
|
use crate::misc::utils::get_reqwest_client;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct GithubResponse {
|
struct GithubResponse {
|
||||||
items: Vec<GithubProject>,
|
items: Vec<GithubProject>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct GithubProject {
|
struct GithubProject {
|
||||||
repository_url: String,
|
repository_url: String,
|
||||||
number: u32,
|
number: u32,
|
||||||
|
@ -19,7 +19,7 @@ struct GithubProject {
|
||||||
pull_request: GithubPullRequest,
|
pull_request: GithubPullRequest,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct GithubPullRequest {
|
struct GithubPullRequest {
|
||||||
html_url: String,
|
html_url: String,
|
||||||
merged_at: Option<String>,
|
merged_at: Option<String>,
|
||||||
|
|
|
@ -3,10 +3,15 @@ use comrak::nodes::{AstNode, NodeValue};
|
||||||
use comrak::{format_html, parse_document, Arena, ComrakOptions, ListStyleType};
|
use comrak::{format_html, parse_document, Arena, ComrakOptions, ListStyleType};
|
||||||
use lol_html::{element, rewrite_str, RewriteStrSettings};
|
use lol_html::{element, rewrite_str, RewriteStrSettings};
|
||||||
use ramhorns::Content;
|
use ramhorns::Content;
|
||||||
|
use regex::{Captures, Regex};
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
#[derive(Default, Deserialize, Content, Debug)]
|
/// Regular markdown files, no metadata
|
||||||
|
#[derive(Content, Debug, Default, Deserialize)]
|
||||||
|
pub struct FileNoMetadata {}
|
||||||
|
|
||||||
|
#[derive(Content, Debug, Default, Deserialize)]
|
||||||
pub struct FileMetadataBlog {
|
pub struct FileMetadataBlog {
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
pub date: Option<Date>,
|
pub date: Option<Date>,
|
||||||
|
@ -16,7 +21,7 @@ pub struct FileMetadataBlog {
|
||||||
pub toc: Option<bool>,
|
pub toc: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Deserialize, Content, Debug)]
|
#[derive(Content, Debug, Default, Deserialize)]
|
||||||
pub struct FileMetadataContact {
|
pub struct FileMetadataContact {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub custom: Option<bool>,
|
pub custom: Option<bool>,
|
||||||
|
@ -26,7 +31,7 @@ pub struct FileMetadataContact {
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Deserialize, Content, Debug)]
|
#[derive(Content, Debug, Default, Deserialize)]
|
||||||
pub struct FileMetadataPortfolio {
|
pub struct FileMetadataPortfolio {
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
pub link: Option<String>,
|
pub link: Option<String>,
|
||||||
|
@ -38,13 +43,15 @@ pub enum TypeFileMetadata {
|
||||||
Blog,
|
Blog,
|
||||||
Contact,
|
Contact,
|
||||||
Portfolio,
|
Portfolio,
|
||||||
|
Cours,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Deserialize, Content, Debug)]
|
#[derive(Content, Debug, Default, Deserialize)]
|
||||||
pub struct FileMetadata {
|
pub struct FileMetadata {
|
||||||
pub blog: Option<FileMetadataBlog>,
|
pub blog: Option<FileMetadataBlog>,
|
||||||
pub contact: Option<FileMetadataContact>,
|
pub contact: Option<FileMetadataContact>,
|
||||||
pub portfolio: Option<FileMetadataPortfolio>,
|
pub portfolio: Option<FileMetadataPortfolio>,
|
||||||
|
pub cours: Option<FileNoMetadata>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Content, Debug, Clone)]
|
#[derive(Content, Debug, Clone)]
|
||||||
|
@ -164,12 +171,35 @@ fn custom_img_size(html: String) -> String {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn math_processing(regex: &str, source: &str) -> String {
|
||||||
|
Regex::new(regex)
|
||||||
|
.unwrap()
|
||||||
|
.replace_all(source, |captures: &Captures| {
|
||||||
|
captures
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.filter_map(|capture| {
|
||||||
|
capture.map(|m| format!("<span data-katex=\"{}\"></span>", m.as_str()))
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("")
|
||||||
|
})
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
/// Transform markdown string to File structure
|
/// Transform markdown string to File structure
|
||||||
fn read(raw_text: &str, metadata_type: TypeFileMetadata) -> File {
|
fn read(raw_text: &str, metadata_type: TypeFileMetadata) -> File {
|
||||||
let arena = Arena::new();
|
let arena = Arena::new();
|
||||||
|
|
||||||
|
// Transform LaTeX formulas
|
||||||
|
let text = math_processing(
|
||||||
|
r"(?U)(\$[^\$|\n]+\$)[^\$]",
|
||||||
|
&math_processing(r"(?U)(\$\$[^\$|\n]+\$\$)", raw_text),
|
||||||
|
);
|
||||||
|
println!("{}", text);
|
||||||
|
|
||||||
let options = get_options();
|
let options = get_options();
|
||||||
let root = parse_document(&arena, raw_text, &options);
|
let root = parse_document(&arena, &text, &options);
|
||||||
|
|
||||||
// Find metadata
|
// Find metadata
|
||||||
let metadata = get_metadata(root, metadata_type);
|
let metadata = get_metadata(root, metadata_type);
|
||||||
|
@ -182,6 +212,7 @@ fn read(raw_text: &str, metadata_type: TypeFileMetadata) -> File {
|
||||||
format_html(root, &options, &mut html).unwrap();
|
format_html(root, &options, &mut html).unwrap();
|
||||||
|
|
||||||
let mut html_content = String::from_utf8(html).unwrap();
|
let mut html_content = String::from_utf8(html).unwrap();
|
||||||
|
/* println!("{}", html_content); */
|
||||||
|
|
||||||
html_content = custom_img_size(html_content);
|
html_content = custom_img_size(html_content);
|
||||||
|
|
||||||
|
@ -227,6 +258,10 @@ pub fn get_metadata<'a>(root: &'a AstNode<'a>, mtype: TypeFileMetadata) -> FileM
|
||||||
portfolio: Some(deserialize_metadata(text)),
|
portfolio: Some(deserialize_metadata(text)),
|
||||||
..FileMetadata::default()
|
..FileMetadata::default()
|
||||||
},
|
},
|
||||||
|
TypeFileMetadata::Cours => FileMetadata {
|
||||||
|
cours: Some(deserialize_metadata(text)),
|
||||||
|
..FileMetadata::default()
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
}) {
|
}) {
|
||||||
|
@ -244,6 +279,10 @@ pub fn get_metadata<'a>(root: &'a AstNode<'a>, mtype: TypeFileMetadata) -> FileM
|
||||||
portfolio: Some(FileMetadataPortfolio::default()),
|
portfolio: Some(FileMetadataPortfolio::default()),
|
||||||
..FileMetadata::default()
|
..FileMetadata::default()
|
||||||
},
|
},
|
||||||
|
TypeFileMetadata::Cours => FileMetadata {
|
||||||
|
cours: Some(FileNoMetadata::default()),
|
||||||
|
..FileMetadata::default()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ struct PortfolioTemplate {
|
||||||
closed: Option<Vec<Project>>,
|
closed: Option<Vec<Project>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Content, Clone, Debug)]
|
#[derive(Clone, Content, Debug)]
|
||||||
struct Project {
|
struct Project {
|
||||||
name: String,
|
name: String,
|
||||||
url: String,
|
url: String,
|
||||||
|
@ -35,7 +35,7 @@ struct Project {
|
||||||
pulls_closed: Vec<Pull>,
|
pulls_closed: Vec<Pull>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Content, Clone, Debug)]
|
#[derive(Clone, Content, Debug)]
|
||||||
struct Pull {
|
struct Pull {
|
||||||
url: String,
|
url: String,
|
||||||
id: u32,
|
id: u32,
|
||||||
|
|
|
@ -1,9 +1,128 @@
|
||||||
use actix_web::{get, Responder};
|
use std::path::Path;
|
||||||
|
|
||||||
|
use actix_web::{get, web, Responder};
|
||||||
|
use ramhorns::Content;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
config::Config,
|
||||||
|
misc::{
|
||||||
|
markdown::{read_file, File, TypeFileMetadata},
|
||||||
|
utils::{make_kw, Html},
|
||||||
|
},
|
||||||
|
template::{Infos, NavBar},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct PathRequest {
|
||||||
|
q: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/cours")]
|
#[get("/cours")]
|
||||||
async fn page() -> impl Responder {
|
async fn page(info: web::Query<PathRequest>, config: web::Data<Config>) -> impl Responder {
|
||||||
// Page de notes de cours
|
Html(build_page(info, config.get_ref().to_owned()))
|
||||||
// Cf. https://univ.mylloon.fr/
|
}
|
||||||
// Cf. https://github.com/xy2z/PineDocs
|
|
||||||
actix_web::web::Redirect::to("/")
|
#[derive(Content, Debug)]
|
||||||
|
struct CoursTemplate {
|
||||||
|
navbar: NavBar,
|
||||||
|
filetree: String,
|
||||||
|
content: Option<File>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct FileNode {
|
||||||
|
name: String,
|
||||||
|
is_dir: bool,
|
||||||
|
children: Vec<FileNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the filetree
|
||||||
|
fn get_filetree(dir_path: &str, exclusion_list: &[&str]) -> FileNode {
|
||||||
|
let entries = std::fs::read_dir(dir_path).unwrap();
|
||||||
|
|
||||||
|
let mut children = Vec::new();
|
||||||
|
for entry in entries.filter_map(Result::ok) {
|
||||||
|
let entry_path = entry.path();
|
||||||
|
let entry_name = entry_path.file_name().and_then(|n| n.to_str()).unwrap();
|
||||||
|
|
||||||
|
// We should support regex?
|
||||||
|
if !exclusion_list.contains(&entry_name) {
|
||||||
|
let filename = entry_name.to_string();
|
||||||
|
if entry_path.is_file() {
|
||||||
|
children.push(FileNode {
|
||||||
|
name: filename,
|
||||||
|
is_dir: false,
|
||||||
|
children: vec![],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
children.push(get_filetree(entry_path.to_str().unwrap(), exclusion_list));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileNode {
|
||||||
|
name: Path::new(dir_path)
|
||||||
|
.file_name()
|
||||||
|
.unwrap()
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string(),
|
||||||
|
is_dir: true,
|
||||||
|
children,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a page content
|
||||||
|
fn get_content(
|
||||||
|
cours_dir: &str,
|
||||||
|
path: &web::Query<PathRequest>,
|
||||||
|
exclusion_list: &[&str],
|
||||||
|
) -> Option<File> {
|
||||||
|
let filename = match &path.q {
|
||||||
|
Some(q) => q,
|
||||||
|
None => "index.md",
|
||||||
|
};
|
||||||
|
|
||||||
|
// We should support regex?
|
||||||
|
if exclusion_list
|
||||||
|
.iter()
|
||||||
|
.any(|&excluded_term| filename.contains(excluded_term))
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
read_file(&format!("{cours_dir}/{filename}"), TypeFileMetadata::Cours)
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[once(time = 60)]
|
||||||
|
// TODO: Uncomment before release
|
||||||
|
fn build_page(info: web::Query<PathRequest>, config: Config) -> String {
|
||||||
|
let cours_dir = "data/cours";
|
||||||
|
let exclusion_list = [];
|
||||||
|
let filetree = get_filetree(cours_dir, &exclusion_list);
|
||||||
|
|
||||||
|
config.tmpl.render(
|
||||||
|
"cours.html",
|
||||||
|
CoursTemplate {
|
||||||
|
navbar: NavBar {
|
||||||
|
cours: true,
|
||||||
|
..NavBar::default()
|
||||||
|
},
|
||||||
|
filetree: serde_json::to_string(&filetree).unwrap(),
|
||||||
|
content: get_content(cours_dir, &info, &exclusion_list),
|
||||||
|
},
|
||||||
|
Infos {
|
||||||
|
page_title: Some("Cours".into()),
|
||||||
|
page_desc: Some("Cours à l'univ".into()),
|
||||||
|
page_kw: make_kw(&[
|
||||||
|
"cours",
|
||||||
|
"études",
|
||||||
|
"université",
|
||||||
|
"licence",
|
||||||
|
"master",
|
||||||
|
"notes",
|
||||||
|
"digital garden",
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ pub struct Template {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Structure used by /routes/*.rs
|
/// Structure used by /routes/*.rs
|
||||||
#[derive(Default, Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Infos {
|
pub struct Infos {
|
||||||
/// Title
|
/// Title
|
||||||
pub page_title: Option<String>,
|
pub page_title: Option<String>,
|
||||||
|
|
47
static/css/cours.css
Normal file
47
static/css/cours.css
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/* Filetree */
|
||||||
|
aside {
|
||||||
|
float: left;
|
||||||
|
margin-left: 20px;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
aside ul {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
aside li {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Element */
|
||||||
|
aside li:before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: -0.2em;
|
||||||
|
left: -1em;
|
||||||
|
height: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
aside li.collapsed > ul {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
aside li.directory::before {
|
||||||
|
content: "+";
|
||||||
|
}
|
||||||
|
|
||||||
|
aside li:not(.collapsed).directory::before {
|
||||||
|
content: "-";
|
||||||
|
}
|
||||||
|
|
||||||
|
aside li.directory {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
aside {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,14 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Ces fontes sont distribuées gratuitement sous Licence publique Creative Commons Attribution 4.0 International :
|
|
||||||
https://creativecommons.org/licenses/by/4.0/legalcode.fr
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
These fonts are freely available under Creative Commons Attribution 4.0 International Public License:
|
|
||||||
https://creativecommons.org/licenses/by/4.0/legalcode
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Luciole © Laurent Bourcellier & Jonathan Perez
|
|
|
@ -65,3 +65,9 @@ header nav a:hover {
|
||||||
.bold {
|
.bold {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
header nav {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
104
static/js/cours.js
Normal file
104
static/js/cours.js
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
/**
|
||||||
|
* Build the filetree
|
||||||
|
* @param {HTMLElement} parent Root element of the filetree
|
||||||
|
* @param {{name: string, is_dir: boolean, children: any[]}} data FileNode
|
||||||
|
* @param {string} location Current location, used for links creation
|
||||||
|
*/
|
||||||
|
const buildFileTree = (parent, data, location) => {
|
||||||
|
const ul = document.createElement("ul");
|
||||||
|
data.forEach((item) => {
|
||||||
|
const li = document.createElement("li");
|
||||||
|
li.classList.add(item.is_dir ? "directory" : "file");
|
||||||
|
|
||||||
|
if (item.is_dir) {
|
||||||
|
// Directory
|
||||||
|
li.textContent = item.name;
|
||||||
|
li.classList.add("collapsed");
|
||||||
|
|
||||||
|
// Toggle collapsing on click
|
||||||
|
li.addEventListener("click", function (e) {
|
||||||
|
if (e.target === li) {
|
||||||
|
li.classList.toggle("collapsed");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// File
|
||||||
|
const url = window.location.href.split("?")[0];
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.text = item.name;
|
||||||
|
a.href = `${url}?q=${location}${item.name}`;
|
||||||
|
li.appendChild(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.appendChild(li);
|
||||||
|
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
buildFileTree(
|
||||||
|
li,
|
||||||
|
item.children,
|
||||||
|
item.is_dir ? location + `${item.name}/` : location
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
parent.appendChild(ul);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uncollapse elements from the deepest element
|
||||||
|
* @param {HTMLLIElement} element Element to uncollapse
|
||||||
|
*/
|
||||||
|
const uncollapse = (element) => {
|
||||||
|
if (element) {
|
||||||
|
element.classList.remove("collapsed");
|
||||||
|
uncollapse(element.parentElement.closest("li"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the deepest opened directory
|
||||||
|
* @param {string[]} path Current path we are looking at, init with fullpath
|
||||||
|
* @param {NodeListOf<ChildNode>} options Options we have, init with list root
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
const deepestNodeOpened = (path, options) => {
|
||||||
|
// Iterate over possible options
|
||||||
|
for (let i = 0; i < options.length; ++i) {
|
||||||
|
// If the directory and the current path match
|
||||||
|
if (decodeURI(path[0]) === options[i].firstChild.nodeValue) {
|
||||||
|
if (path.length === 1) {
|
||||||
|
// We found it
|
||||||
|
return options[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue the search
|
||||||
|
return deepestNodeOpened(
|
||||||
|
path.slice(1),
|
||||||
|
options[i].querySelector("ul").childNodes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("load", () => {
|
||||||
|
// Build the filetree
|
||||||
|
const fileTreeElement = document.getElementsByTagName("aside")[0];
|
||||||
|
const dataElement = fileTreeElement.getElementsByTagName("span")[0];
|
||||||
|
|
||||||
|
buildFileTree(
|
||||||
|
fileTreeElement,
|
||||||
|
JSON.parse(dataElement.getAttribute("data-json")).children,
|
||||||
|
""
|
||||||
|
);
|
||||||
|
dataElement.remove();
|
||||||
|
|
||||||
|
// Open nested openeded directories
|
||||||
|
const fullpath = window.location.href.split("?")[1].substring(2);
|
||||||
|
const path = fullpath.substring(0, fullpath.lastIndexOf("/"));
|
||||||
|
const last_openeded = deepestNodeOpened(
|
||||||
|
path.split("/"),
|
||||||
|
fileTreeElement.querySelector("ul").childNodes
|
||||||
|
);
|
||||||
|
|
||||||
|
uncollapse(last_openeded);
|
||||||
|
});
|
|
@ -10,17 +10,26 @@ window.addEventListener("load", () => {
|
||||||
la: "leftarrow",
|
la: "leftarrow",
|
||||||
RA: "Rightarrow",
|
RA: "Rightarrow",
|
||||||
LA: "Leftarrow",
|
LA: "Leftarrow",
|
||||||
|
u: "mu",
|
||||||
})
|
})
|
||||||
)[Symbol.iterator]()) {
|
)[Symbol.iterator]()) {
|
||||||
macros[`\\${item[0]}`] = `\\${item[1]}`;
|
macros[`\\${item[0]}`] = `\\${item[1]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMathInElement(document.body, {
|
document.querySelectorAll("span[data-katex]").forEach((element) => {
|
||||||
delimiters: [
|
const rawLaTeXFormula = element.getAttribute("data-katex");
|
||||||
{ left: "$$", right: "$$", display: true },
|
const displayMode = rawLaTeXFormula.startsWith("$$");
|
||||||
{ left: "$", right: "$", display: false },
|
const strip = displayMode ? 2 : 1;
|
||||||
],
|
|
||||||
throwOnError: false,
|
katex.render(
|
||||||
macros,
|
rawLaTeXFormula.slice(strip, rawLaTeXFormula.length - strip),
|
||||||
|
element,
|
||||||
|
{
|
||||||
|
throwOnError: false,
|
||||||
|
macros,
|
||||||
|
displayMode,
|
||||||
|
output: "mathml",
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
26
templates/cours.html
Normal file
26
templates/cours.html
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head dir="ltr">
|
||||||
|
{{>head.html}}
|
||||||
|
<link rel="stylesheet" href="/css/cours.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>{{>navbar.html}}</header>
|
||||||
|
|
||||||
|
<aside>
|
||||||
|
<span data-json="{{#data}}{{filetree}}{{/data}} "></span>
|
||||||
|
</aside>
|
||||||
|
<main>
|
||||||
|
{{#data}} {{^content}}
|
||||||
|
<p>Fichier introuvable</p>
|
||||||
|
{{/content}} {{#content}}
|
||||||
|
<article>{{&content}}</article>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
{{#metadata}} {{#mermaid}}{{>libs/mermaid_footer.html}}{{/mermaid}}
|
||||||
|
{{#math}}{{>libs/katex_footer.html}}{{/math}}
|
||||||
|
{{#syntax_highlight}}{{>libs/hljs_footer.html}}{{/syntax_highlight}}
|
||||||
|
{{/metadata}} {{/content}} {{/data}}
|
||||||
|
<script src="/js/cours.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -4,10 +4,4 @@
|
||||||
integrity="sha384-j/ZricySXBnNMJy9meJCtyXTKMhIJ42heyr7oAdxTDBy/CYA9hzpMo+YTNV5C+1X"
|
integrity="sha384-j/ZricySXBnNMJy9meJCtyXTKMhIJ42heyr7oAdxTDBy/CYA9hzpMo+YTNV5C+1X"
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
></script>
|
></script>
|
||||||
<script
|
|
||||||
defer
|
|
||||||
src="//cdn.jsdelivr.net/npm/katex@0.16.6/dist/contrib/auto-render.min.js"
|
|
||||||
integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05"
|
|
||||||
crossorigin="anonymous"
|
|
||||||
></script>
|
|
||||||
<script src="/js/libs/katex.js"></script>
|
<script src="/js/libs/katex.js"></script>
|
||||||
|
|
|
@ -45,9 +45,8 @@
|
||||||
|
|
||||||
<p><a
|
<p><a
|
||||||
class="_ {{#cours}}bold{{/cours}}"
|
class="_ {{#cours}}bold{{/cours}}"
|
||||||
href="https://univ.mylloon.fr"
|
href="/cours"
|
||||||
title="Page des notes de cours"
|
title="Page des notes de cours"
|
||||||
target="_blank"
|
|
||||||
>Cours</a
|
>Cours</a
|
||||||
></p>
|
></p>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue