WIP Navigation sorting

This commit is contained in:
ef3d0c3e 2024-08-03 18:43:54 +02:00
parent 90cf691737
commit 0982527944
5 changed files with 131 additions and 20 deletions

View file

@ -25,3 +25,8 @@ function make_doc(categories, title, page_title)
nml.variable.insert("compiler.output", page_title .. ".html") nml.variable.insert("compiler.output", page_title .. ".html")
end end
>@ >@
@@style.section = {
"link_pos": "Before",
"link": ["", "🔗 ", " "]
}

View file

@ -7,7 +7,7 @@ use super::compiler::Target;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct NavEntry { pub struct NavEntry {
pub(self) entries: Vec<(String, String)>, pub(self) entries: Vec<(String, String, Option<String>)>,
pub(self) children: HashMap<String, NavEntry>, pub(self) children: HashMap<String, NavEntry>,
} }
@ -34,7 +34,7 @@ impl NavEntry {
depth: usize, depth: usize,
) { ) {
// Orphans = Links // Orphans = Links
for (title, path) in &entry.entries { for (title, path, _) in &entry.entries {
result.push_str( result.push_str(
format!( format!(
r#"<li><a href="{}">{}</a></li>"#, r#"<li><a href="{}">{}</a></li>"#,
@ -75,6 +75,36 @@ impl NavEntry {
} }
result result
} }
/// Gets the insert index of the entry inside an already sorted entry list
fn sort_entry(
title: &String,
previous: &Option<String>,
entries: &Vec<(String, String, Option<String>)>,
) -> usize {
let mut insert_at = 0;
if let Some(previous) = &previous
// Using sort key
{
for (i, (ent_title, _, _)) in entries.iter().enumerate() {
if ent_title == previous {
insert_at = i + 1;
break;
}
}
}
// Then sort alphabetically
for (ent_title, _, ent_previous) in entries.iter().skip(insert_at) {
if (previous.is_some() && ent_previous != previous) || ent_title > title {
break;
}
insert_at += 1;
}
insert_at
}
} }
pub fn create_navigation(docs: &Vec<CompiledDocument>) -> Result<NavEntry, String> { pub fn create_navigation(docs: &Vec<CompiledDocument>) -> Result<NavEntry, String> {
@ -83,12 +113,16 @@ pub fn create_navigation(docs: &Vec<CompiledDocument>) -> Result<NavEntry, Strin
children: HashMap::new(), children: HashMap::new(),
}; };
// All paths (for duplicate checking)
let mut all_paths = HashMap::new();
for doc in docs { for doc in docs {
let cat = doc.get_variable("nav.category"); let cat = doc.get_variable("nav.category");
let subcat = doc.get_variable("nav.subcategory"); let subcat = doc.get_variable("nav.subcategory");
let title = doc let title = doc
.get_variable("nav.title") .get_variable("nav.title")
.or(doc.get_variable("doc.title")); .or(doc.get_variable("doc.title"));
let previous = doc.get_variable("nav.previous").map(|s| s.clone());
let path = doc.get_variable("compiler.output"); let path = doc.get_variable("compiler.output");
let (title, path) = match (title, path) { let (title, path) = match (title, path) {
@ -142,8 +176,67 @@ pub fn create_navigation(docs: &Vec<CompiledDocument>) -> Result<NavEntry, Strin
&mut nav &mut nav
}; };
pent.entries.push((title.clone(), path.clone())) // Find duplicates titles in current parent
for (ent_title, _, _) in &pent.entries {
if ent_title == title {
return Err(format!(
"Conflicting entry title `{title}` for entries with the same parent: ({})",
pent.entries
.iter()
.map(|(title, _, _)| title.clone())
.collect::<Vec<_>>()
.join(", ")
));
}
}
// Find duplicate paths
if let Some(dup_title) = all_paths.get(path) {
return Err(format!("Conflicting paths: `{path}`. Previously used for entry: `{dup_title}`, conflicting use in `{title}`"));
}
all_paths.insert(path.clone(), title.clone());
pent.entries.insert(
NavEntry::sort_entry(title, &previous, &pent.entries),
(title.clone(), path.clone(), previous),
);
} }
Ok(nav) Ok(nav)
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sort() {
let entries: Vec<(String, String, Option<String>)> = vec![
("Root".into(), "".into(), None),
("First".into(), "".into(), Some("Root".into())),
("1".into(), "".into(), Some("First".into())),
("2".into(), "".into(), Some("First".into())),
];
assert_eq!(
NavEntry::sort_entry(&"E".into(), &Some("Root".into()), &entries),
1
);
assert_eq!(
NavEntry::sort_entry(&"G".into(), &Some("Root".into()), &entries),
2
);
// Orphans
assert_eq!(NavEntry::sort_entry(&"Q".into(), &None, &entries), 0);
assert_eq!(NavEntry::sort_entry(&"S".into(), &None, &entries), 4);
assert_eq!(
NavEntry::sort_entry(&"1.1".into(), &Some("First".into()), &entries),
3
);
assert_eq!(
NavEntry::sort_entry(&"2.1".into(), &Some("First".into()), &entries),
4
);
}
}

View file

@ -81,7 +81,7 @@ impl ReferenceRule {
), ),
); );
Self { Self {
re: [Regex::new(r"§\{(.*)\}(\[((?:\\.|[^\\\\])*?)\])?").unwrap()], re: [Regex::new(r"§\{(.*?)\}(\[((?:\\.|[^\\\\])*?)\])?").unwrap()],
properties: PropertyParser{ properties: props }, properties: PropertyParser{ properties: props },
} }
} }

View file

@ -46,16 +46,17 @@ impl Element for Section {
match compiler.target() { match compiler.target() {
Target::HTML => { Target::HTML => {
// Section numbering // Section numbering
let number = if (self.kind & section_kind::NO_NUMBER) == section_kind::NO_NUMBER { let number = if (self.kind & section_kind::NO_NUMBER) != section_kind::NO_NUMBER {
let numbering = compiler.section_counter(self.depth); let numbering = compiler.section_counter(self.depth);
let number = " ".to_string()
+ numbering let mut result = String::new();
.iter() for num in numbering.iter()
.map(|n| n.to_string()) {
.collect::<Vec<_>>() result = result + num.to_string().as_str() + ".";
.join(".") }
.as_str(); result += " ";
number
result
} else { } else {
String::new() String::new()
}; };
@ -71,8 +72,10 @@ impl Element for Section {
let refname = Compiler::refname(compiler.target(), self.title.as_str()); let refname = Compiler::refname(compiler.target(), self.title.as_str());
let link = format!( let link = format!(
"<a class=\"section-link\" href=\"#{refname}\">{}</a>", "{}<a class=\"section-link\" href=\"#{refname}\">{}</a>{}",
Compiler::sanitize(compiler.target(), self.style.link.as_str()) Compiler::sanitize(compiler.target(), self.style.link[0].as_str()),
Compiler::sanitize(compiler.target(), self.style.link[1].as_str()),
Compiler::sanitize(compiler.target(), self.style.link[2].as_str())
); );
if self.style.link_pos == SectionLinkPos::After { if self.style.link_pos == SectionLinkPos::After {
@ -123,7 +126,7 @@ impl ReferenceableElement for Section {
); );
Ok(format!( Ok(format!(
"<a class=\"section-ref\" href=\"#{}\">{caption}</a>", "<a class=\"section-reference\" href=\"#{}\">{caption}</a>",
Compiler::refname(compiler.target(), self.title.as_str()) Compiler::refname(compiler.target(), self.title.as_str())
)) ))
} }
@ -390,14 +393,14 @@ mod section_style {
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct SectionStyle { pub struct SectionStyle {
pub link_pos: SectionLinkPos, pub link_pos: SectionLinkPos,
pub link: String, pub link: [String; 3],
} }
impl Default for SectionStyle { impl Default for SectionStyle {
fn default() -> Self { fn default() -> Self {
Self { Self {
link_pos: SectionLinkPos::After, link_pos: SectionLinkPos::Before,
link: "🔗".to_string(), link: ["".into(), "🔗".into(), " ".into()],
} }
} }
} }

View file

@ -1,6 +1,6 @@
use std::{cell::{RefCell, RefMut}, collections::HashMap, rc::Rc}; use std::{cell::{RefCell, RefMut}, collections::HashMap, rc::Rc};
use crate::{document::{document::Document, element::Element}, lua::kernel::{Kernel, KernelHolder}, parser::{parser::{Parser, ReportColors}, rule::Rule, source::{Cursor, Source}, state::StateHolder}}; use crate::{document::{document::Document, element::Element, style::{ElementStyle, StyleHolder}}, lua::kernel::{Kernel, KernelHolder}, parser::{parser::{Parser, ReportColors}, rule::Rule, source::{Cursor, Source}, state::StateHolder}};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct LineCursor pub struct LineCursor
@ -146,3 +146,13 @@ impl KernelHolder for LsParser
self.get_kernel(name.as_str()).unwrap() self.get_kernel(name.as_str()).unwrap()
} }
} }
impl StyleHolder for LsParser {
fn styles(&self) -> std::cell::Ref<'_, HashMap<String, Rc<dyn ElementStyle>>> {
todo!()
}
fn styles_mut(&self) -> RefMut<'_, HashMap<String, Rc<dyn ElementStyle>>> {
todo!()
}
}