Collapse navbar

This commit is contained in:
ef3d0c3e 2024-07-29 16:45:14 +02:00
parent c8d35a7dc3
commit 8721f97b98
8 changed files with 134 additions and 105 deletions

View file

@ -74,8 +74,8 @@ impl Compiler {
self.cache.as_ref().map(RefCell::borrow_mut) self.cache.as_ref().map(RefCell::borrow_mut)
} }
pub fn sanitize<S: AsRef<str>>(&self, str: S) -> String { pub fn sanitize<S: AsRef<str>>(target: Target, str: S) -> String {
match self.target { match target {
Target::HTML => str Target::HTML => str
.as_ref() .as_ref()
.replace("&", "&amp;") .replace("&", "&amp;")
@ -109,14 +109,14 @@ impl Compiler {
result += "<!DOCTYPE HTML><html><head>"; result += "<!DOCTYPE HTML><html><head>";
result += "<meta charset=\"UTF-8\">"; result += "<meta charset=\"UTF-8\">";
if let Some(page_title) = get_variable_or_error(document, "html.page_title") { if let Some(page_title) = get_variable_or_error(document, "html.page_title") {
result += format!("<title>{}</title>", self.sanitize(page_title.to_string())) result += format!("<title>{}</title>", Compiler::sanitize(self.target(), page_title.to_string()))
.as_str(); .as_str();
} }
if let Some(css) = document.get_variable("html.css") { if let Some(css) = document.get_variable("html.css") {
result += format!( result += format!(
"<link rel=\"stylesheet\" href=\"{}\">", "<link rel=\"stylesheet\" href=\"{}\">",
self.sanitize(css.to_string()) Compiler::sanitize(self.target(), css.to_string())
) )
.as_str(); .as_str();
} }

View file

@ -1,47 +1,73 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::compiler::compiler::Compiler;
use super::compiler::CompiledDocument; use super::compiler::CompiledDocument;
use super::compiler::Target; use super::compiler::Target;
#[derive(Debug)] #[derive(Debug, Default)]
pub struct NavEntry { pub struct NavEntry {
pub(crate) name: String, pub(self) entries: Vec<(String, String)>,
pub(crate) path: Option<String>, pub(self) children: HashMap<String, NavEntry>,
pub(crate) children: Option<HashMap<String, NavEntry>>,
} }
#[derive(Debug)] impl NavEntry {
pub struct Navigation { // FIXME: Sanitize
pub(crate) entries: HashMap<String, NavEntry>, pub fn compile(&self, target: Target, doc: &CompiledDocument) -> String {
} let categories = vec![
doc.get_variable("nav.category").map_or("", |s| s.as_str()),
doc.get_variable("nav.subcategory")
.map_or("", |s| s.as_str()),
];
impl Navigation {
pub fn compile(&self, target: Target) -> String {
let mut result = String::new(); let mut result = String::new();
match target { match target {
Target::HTML => { Target::HTML => {
result += r#"<ul id="navbar">"#; result += r#"<ul id="navbar">"#;
fn process(result: &mut String, name: &String, ent: &NavEntry, depth: usize) { fn process(
let ent_path = ent target: Target,
.path categories: &Vec<&str>,
.as_ref() did_match: bool,
.map_or("#".to_string(), |path| path.clone()); result: &mut String,
result entry: &NavEntry,
.push_str(format!(r#"<li><a href="{ent_path}">{name}</a></li>"#).as_str()); depth: usize,
) {
// Orphans = Links
for (title, path) in &entry.entries {
result.push_str(
format!(
r#"<li><a href="{}">{}</a></li>"#,
Compiler::sanitize(target, path),
Compiler::sanitize(target, title)
)
.as_str(),
);
}
if let Some(children) = ent.children.as_ref() { // Recurse
for (name, ent) in &entry.children {
let is_match = if did_match {
categories.get(depth) == Some(&name.as_str())
} else {
false || depth == 0
};
result.push_str("<li>");
result.push_str(
format!(
"<details{}><summary>{}</summary>",
["", " open"][is_match as usize],
Compiler::sanitize(target, name)
)
.as_str(),
);
result.push_str("<ul>"); result.push_str("<ul>");
for (name, ent) in children { process(target, categories, is_match, result, ent, depth + 1);
process(result, name, ent, depth + 1); result.push_str("</ul></details></li>");
}
result.push_str("</ul>");
} }
} }
for (name, ent) in &self.entries { process(target, &categories, true, &mut result, self, 0);
process(&mut result, name, ent, 0);
}
result += r#"</ul>"#; result += r#"</ul>"#;
} }
@ -51,9 +77,10 @@ impl Navigation {
} }
} }
pub fn create_navigation(docs: &Vec<CompiledDocument>) -> Result<Navigation, String> { pub fn create_navigation(docs: &Vec<CompiledDocument>) -> Result<NavEntry, String> {
let mut nav = Navigation { let mut nav = NavEntry {
entries: HashMap::new(), entries: vec![],
children: HashMap::new(),
}; };
for doc in docs { for doc in docs {
@ -64,71 +91,60 @@ pub fn create_navigation(docs: &Vec<CompiledDocument>) -> Result<Navigation, Str
.or(doc.get_variable("doc.title")); .or(doc.get_variable("doc.title"));
let path = doc.get_variable("compiler.output"); let path = doc.get_variable("compiler.output");
let (cat, title, path) = match (cat, title, path) { let (title, path) = match (title, path) {
(Some(cat), Some(title), Some(path)) => (cat, title, path), (Some(title), Some(path)) => (title, path),
_ => { _ => {
println!("Skipping navigation generation for `{}`", doc.input); eprintln!("Skipping navigation generation for `{}`, must have a defined `@nav.title` and `@compiler.output`", doc.input);
continue; continue;
} }
}; };
if let Some(subcat) = subcat { let pent = if let Some(subcat) = subcat {
// Get parent entry let cat = match cat {
let mut pent = match nav.entries.get_mut(cat.as_str()) { Some(cat) => cat,
Some(pent) => pent,
None => { None => {
// Create parent entry eprintln!(
nav.entries.insert( "Skipping `{}`: No `@nav.category`, but `@nav.subcategory` is set",
cat.clone(), doc.input
NavEntry {
name: cat.clone(),
path: None,
children: Some(HashMap::new()),
},
); );
nav.entries.get_mut(cat.as_str()).unwrap() continue;
} }
}; };
// Insert into parent let mut cat_ent = match nav.children.get_mut(cat.as_str()) {
if let Some(previous) = pent.children.as_mut().unwrap().insert( Some(cat_ent) => cat_ent,
subcat.clone(), None => {
NavEntry { // Insert
name: subcat.clone(), nav.children.insert(cat.clone(), NavEntry::default());
path: Some(path.to_string()), nav.children.get_mut(cat.as_str()).unwrap()
children: None, }
}, };
) {
return Err(format!( match cat_ent.children.get_mut(subcat.as_str()) {
"Duplicate subcategory:\n{subcat}\nclashes with:\n{previous:#?}" Some(subcat_ent) => subcat_ent,
)); None => {
// Insert
cat_ent.children.insert(subcat.clone(), NavEntry::default());
cat_ent.children.get_mut(subcat.as_str()).unwrap()
}
}
} else if let Some(cat) = cat {
match nav.children.get_mut(cat.as_str()) {
Some(cat_ent) => cat_ent,
None => {
// Insert
nav.children.insert(cat.clone(), NavEntry::default());
nav.children.get_mut(cat.as_str()).unwrap()
}
} }
} else { } else {
// Get entry &mut nav
let mut ent = match nav.entries.get_mut(cat.as_str()) { };
Some(ent) => ent,
None => {
// Create parent entry
nav.entries.insert(
cat.clone(),
NavEntry {
name: cat.clone(),
path: None,
children: Some(HashMap::new()),
},
);
nav.entries.get_mut(cat.as_str()).unwrap()
}
};
if let Some(path) = ent.path.as_ref() { pent.entries.push((title.clone(), path.clone()))
return Err(format!(
"Duplicate category:\n{subcat:#?}\nwith previous path:\n{path}"
));
}
ent.path = Some(path.to_string());
}
} }
println!("{nav:#?}");
Ok(nav) Ok(nav)
} }

View file

@ -117,7 +117,7 @@ impl Code {
if let Some(name) = &self.name { if let Some(name) = &self.name {
result += format!( result += format!(
"<div class=\"code-block-title\">{}</div>", "<div class=\"code-block-title\">{}</div>",
compiler.sanitize(name.as_str()) Compiler::sanitize(compiler.target(), name.as_str())
) )
.as_str(); .as_str();
} }
@ -612,7 +612,13 @@ impl RegexRule for CodeRule {
bindings.push(( bindings.push((
"push_block".to_string(), "push_block".to_string(),
lua.create_function( lua.create_function(
|_, (language, name, content, line_offset): (String, Option<String>, String, Option<usize>)| { |_,
(language, name, content, line_offset): (
String,
Option<String>,
String,
Option<usize>,
)| {
CTX.with_borrow(|ctx| { CTX.with_borrow(|ctx| {
ctx.as_ref().map(|ctx| { ctx.as_ref().map(|ctx| {
let theme = ctx let theme = ctx

View file

@ -45,13 +45,13 @@ impl Element for Link {
match compiler.target() { match compiler.target() {
Target::HTML => Ok(format!( Target::HTML => Ok(format!(
"<a href=\"{}\">{}</a>", "<a href=\"{}\">{}</a>",
compiler.sanitize(self.url.as_str()), Compiler::sanitize(compiler.target(), self.url.as_str()),
compiler.sanitize(self.name.as_str()), Compiler::sanitize(compiler.target(), self.name.as_str()),
)), )),
Target::LATEX => Ok(format!( Target::LATEX => Ok(format!(
"\\href{{{}}}{{{}}}", "\\href{{{}}}{{{}}}",
compiler.sanitize(self.url.as_str()), Compiler::sanitize(compiler.target(), self.url.as_str()),
compiler.sanitize(self.name.as_str()), Compiler::sanitize(compiler.target(), self.name.as_str()),
)), )),
} }
} }

View file

@ -150,18 +150,26 @@ impl Element for Medium {
.map_or(String::new(), |w| format!(r#" style="width:{w};""#)); .map_or(String::new(), |w| format!(r#" style="width:{w};""#));
result.push_str(format!(r#"<div class="medium"{width}>"#).as_str()); result.push_str(format!(r#"<div class="medium"{width}>"#).as_str());
result += match self.media_type { result += match self.media_type {
MediaType::IMAGE => MediaType::IMAGE => format!(r#"<a href="{0}"><img src="{0}"></a>"#, self.uri),
format!(r#"<a href="{0}"><img src="{0}"></a>"#, self.uri), MediaType::VIDEO => format!(
MediaType::VIDEO => r#"<video controls{width}><source src="{0}"></video>"#,
format!(r#"<video controls{width}><source src="{0}"></video>"#, self.uri), self.uri
MediaType::AUDIO => ),
format!(r#"<audio controls src="{0}"{width}></audio>"#, self.uri), MediaType::AUDIO => {
}.as_str(); format!(r#"<audio controls src="{0}"{width}></audio>"#, self.uri)
}
}
.as_str();
let caption = self let caption = self
.caption .caption
.as_ref() .as_ref()
.and_then(|cap| Some(format!(" {}", compiler.sanitize(cap.as_str())))) .and_then(|cap| {
Some(format!(
" {}",
Compiler::sanitize(compiler.target(), cap.as_str())
))
})
.unwrap_or(String::new()); .unwrap_or(String::new());
// Reference // Reference

View file

@ -41,7 +41,7 @@ impl Element for Section {
Target::HTML => Ok(format!( Target::HTML => Ok(format!(
"<h{0}>{1}</h{0}>", "<h{0}>{1}</h{0}>",
self.depth, self.depth,
compiler.sanitize(self.title.as_str()) Compiler::sanitize(compiler.target(), self.title.as_str())
)), )),
Target::LATEX => Err("Unimplemented compiler".to_string()), Target::LATEX => Err("Unimplemented compiler".to_string()),
} }

View file

@ -39,7 +39,7 @@ impl Element for Text {
fn to_string(&self) -> String { format!("{self:#?}") } fn to_string(&self) -> String { format!("{self:#?}") }
fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result<String, String> { fn compile(&self, compiler: &Compiler, _document: &dyn Document) -> Result<String, String> {
Ok(compiler.sanitize(self.content.as_str())) Ok(Compiler::sanitize(compiler.target(), self.content.as_str()))
} }
} }

View file

@ -7,7 +7,6 @@ mod lua;
mod parser; mod parser;
use std::env; use std::env;
use std::fs::OpenOptions;
use std::io::BufWriter; use std::io::BufWriter;
use std::io::Write; use std::io::Write;
use std::process::ExitCode; use std::process::ExitCode;
@ -18,7 +17,6 @@ use compiler::compiler::CompiledDocument;
use compiler::compiler::Compiler; use compiler::compiler::Compiler;
use compiler::compiler::Target; use compiler::compiler::Target;
use compiler::navigation::create_navigation; use compiler::navigation::create_navigation;
use compiler::navigation::Navigation;
use document::document::Document; use document::document::Document;
use getopts::Options; use getopts::Options;
use parser::langparser::LangParser; use parser::langparser::LangParser;
@ -275,7 +273,6 @@ fn main() -> ExitCode {
return ExitCode::FAILURE; return ExitCode::FAILURE;
} }
}; };
let compiled_navigation = navigation.compile(Target::HTML);
// Output // Output
for doc in compiled { for doc in compiled {
@ -290,15 +287,17 @@ fn main() -> ExitCode {
} }
}; };
let nav = navigation.compile(Target::HTML, &doc);
let file = std::fs::File::create(output.clone() + "/" + out_path.as_str()).unwrap(); let file = std::fs::File::create(output.clone() + "/" + out_path.as_str()).unwrap();
let mut writer = BufWriter::new(file); let mut writer = BufWriter::new(file);
write!( write!(
writer, writer,
"{}{}{}{}", "{}{}{}{}",
doc.header, compiled_navigation, doc.body, doc.footer doc.header, nav, doc.body, doc.footer
); ).unwrap();
writer.flush(); writer.flush().unwrap();
} }
return ExitCode::SUCCESS; return ExitCode::SUCCESS;