Collapse navbar
This commit is contained in:
parent
c8d35a7dc3
commit
8721f97b98
8 changed files with 134 additions and 105 deletions
|
@ -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("&", "&")
|
||||
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()),
|
||||
}
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue