diff --git a/src/lib.rs b/src/lib.rs
index 59c4a0f..34bc866 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -126,10 +126,24 @@ pub fn compile_article(
.map_err(|e| format_typst_compile_error(e, &full_source_str, index_byte_start, &index_source))?;
let mut outline = EcoString::new();
- let mut curr_level = 1;
- parse_outline(&mut doc.root, &mut outline, &mut curr_level, include_title);
- for i in (1..curr_level).rev() {
- outline.push_str(" ".repeat(i as usize - 1).as_str());
+ let mut curr_level = 1u32;
+ let mut ul_depth = 0u32;
+ let mut title_h2_pending = !include_title;
+ let mut first_outline_heading = true;
+ parse_outline(
+ &mut doc.root,
+ &mut outline,
+ &mut curr_level,
+ &mut ul_depth,
+ include_title,
+ &mut title_h2_pending,
+ &mut first_outline_heading,
+ );
+ // `ul_depth` counts open `
` tags; it can diverge from `curr_level - 1` when the first
+ // outline heading uses lazy depth (skip title). Always drain by `ul_depth`, not `curr_level`.
+ while ul_depth > 0 {
+ ul_depth -= 1;
+ outline.push_str(" ".repeat(ul_depth as usize).as_str());
outline.push_str("
\n");
}
fs::write(&outline_file, outline.as_bytes()).map_err(|e| {
@@ -179,19 +193,50 @@ pub fn compile_article(
Ok(())
}
+fn heading_level_from_tag(tag: &str) -> Option {
+ match tag {
+ "" => Some(2),
+ "" => Some(3),
+ "" => Some(4),
+ "" => Some(5),
+ "" => Some(6),
+ _ => None,
+ }
+}
+
fn parse_outline(
elem: &mut HtmlElement,
outline: &mut EcoString,
curr_level: &mut u32,
+ ul_depth: &mut u32,
include_title: bool,
+ title_h2_pending: &mut bool,
+ first_outline_heading: &mut bool,
) {
- if matches!(
- elem.tag.to_string().as_str(),
- "" | "" | "" | "" | ""
- ) {
- if !include_title && elem.tag.to_string().as_str() == "" {
- return;
+ let tag = elem.tag.to_string();
+ let tag_ref = tag.as_str();
+
+ if let Some(level) = heading_level_from_tag(tag_ref) {
+ if !include_title && *title_h2_pending {
+ *title_h2_pending = false;
+ if tag_ref == "" {
+ for child in elem.children.make_mut().iter_mut() {
+ if let HtmlNode::Element(e) = child {
+ parse_outline(
+ e,
+ outline,
+ curr_level,
+ ul_depth,
+ include_title,
+ title_h2_pending,
+ first_outline_heading,
+ );
+ }
+ }
+ return;
+ }
}
+ *title_h2_pending = false;
let mut header_text = EcoString::new();
@@ -218,21 +263,26 @@ fn parse_outline(
.trim_matches('-')
.replace("--", "-");
- let level: u32 = elem.tag.to_string().chars().nth(2).unwrap() as u32 - '0' as u32;
+ if *first_outline_heading {
+ *first_outline_heading = false;
+ *curr_level = level.saturating_sub(1);
+ }
while level > *curr_level {
- *curr_level += 1;
- outline.push_str(" ".repeat(*curr_level as usize - 2).as_str());
+ outline.push_str(" ".repeat(*ul_depth as usize).as_str());
outline.push_str("\n");
+ *ul_depth += 1;
+ *curr_level += 1;
}
- while level < *curr_level {
- outline.push_str(" ".repeat(*curr_level as usize - 2).as_str());
- outline.push_str("
\n");
+ while level < *curr_level && *ul_depth > 0 {
*curr_level -= 1;
+ *ul_depth -= 1;
+ outline.push_str(" ".repeat(*ul_depth as usize).as_str());
+ outline.push_str("\n");
}
*curr_level = level;
- outline.push_str(" ".repeat(*curr_level as usize - 1).as_str());
+ outline.push_str(" ".repeat(*ul_depth as usize).as_str());
outline.push_str(
format!(
"
{}\n",
@@ -247,7 +297,17 @@ fn parse_outline(
for child in elem.children.make_mut().iter_mut() {
match child {
- HtmlNode::Element(e) => parse_outline(e, outline, curr_level, include_title),
+ HtmlNode::Element(e) => {
+ parse_outline(
+ e,
+ outline,
+ curr_level,
+ ul_depth,
+ include_title,
+ title_h2_pending,
+ first_outline_heading,
+ );
+ }
_ => {}
}
}