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)
|
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("&", "&")
|
.replace("&", "&")
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()),
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue