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

View file

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

View file

@ -117,7 +117,7 @@ impl Code {
if let Some(name) = &self.name {
result += format!(
"<div class=\"code-block-title\">{}</div>",
compiler.sanitize(name.as_str())
Compiler::sanitize(compiler.target(), name.as_str())
)
.as_str();
}
@ -612,7 +612,13 @@ impl RegexRule for CodeRule {
bindings.push((
"push_block".to_string(),
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.as_ref().map(|ctx| {
let theme = ctx

View file

@ -45,13 +45,13 @@ impl Element for Link {
match compiler.target() {
Target::HTML => Ok(format!(
"<a href=\"{}\">{}</a>",
compiler.sanitize(self.url.as_str()),
compiler.sanitize(self.name.as_str()),
Compiler::sanitize(compiler.target(), self.url.as_str()),
Compiler::sanitize(compiler.target(), self.name.as_str()),
)),
Target::LATEX => Ok(format!(
"\\href{{{}}}{{{}}}",
compiler.sanitize(self.url.as_str()),
compiler.sanitize(self.name.as_str()),
Compiler::sanitize(compiler.target(), self.url.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};""#));
result.push_str(format!(r#"<div class="medium"{width}>"#).as_str());
result += match self.media_type {
MediaType::IMAGE =>
format!(r#"<a href="{0}"><img src="{0}"></a>"#, self.uri),
MediaType::VIDEO =>
format!(r#"<video controls{width}><source src="{0}"></video>"#, self.uri),
MediaType::AUDIO =>
format!(r#"<audio controls src="{0}"{width}></audio>"#, self.uri),
}.as_str();
MediaType::IMAGE => format!(r#"<a href="{0}"><img src="{0}"></a>"#, self.uri),
MediaType::VIDEO => format!(
r#"<video controls{width}><source src="{0}"></video>"#,
self.uri
),
MediaType::AUDIO => {
format!(r#"<audio controls src="{0}"{width}></audio>"#, self.uri)
}
}
.as_str();
let caption = self
.caption
.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());
// Reference

View file

@ -41,7 +41,7 @@ impl Element for Section {
Target::HTML => Ok(format!(
"<h{0}>{1}</h{0}>",
self.depth,
compiler.sanitize(self.title.as_str())
Compiler::sanitize(compiler.target(), self.title.as_str())
)),
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 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;
use std::env;
use std::fs::OpenOptions;
use std::io::BufWriter;
use std::io::Write;
use std::process::ExitCode;
@ -18,7 +17,6 @@ use compiler::compiler::CompiledDocument;
use compiler::compiler::Compiler;
use compiler::compiler::Target;
use compiler::navigation::create_navigation;
use compiler::navigation::Navigation;
use document::document::Document;
use getopts::Options;
use parser::langparser::LangParser;
@ -275,7 +273,6 @@ fn main() -> ExitCode {
return ExitCode::FAILURE;
}
};
let compiled_navigation = navigation.compile(Target::HTML);
// Output
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 mut writer = BufWriter::new(file);
write!(
writer,
"{}{}{}{}",
doc.header, compiled_navigation, doc.body, doc.footer
);
writer.flush();
doc.header, nav, doc.body, doc.footer
).unwrap();
writer.flush().unwrap();
}
return ExitCode::SUCCESS;