use scraper::ElementRef;
use crate::context::Context;
use crate::converter::{Action, Rule};
use crate::dom;
#[derive(Debug, Clone, Copy)]
pub(super) struct List;
impl Rule for List {
fn tags(&self) -> &'static [&'static str] {
&["ul", "ol"]
}
fn apply(&self, content: &str, element: &ElementRef<'_>, _ctx: &mut Context<'_>) -> Action {
let trimmed = content.trim_end_matches('\n');
if trimmed.is_empty() {
return Action::Skip;
}
if dom::has_ancestor(element, "li") {
Action::Replace(format!("\n{trimmed}"))
} else {
Action::Replace(format!("\n\n{trimmed}\n\n"))
}
}
}
#[derive(Debug, Clone, Copy)]
pub(super) struct ListItem;
impl Rule for ListItem {
fn tags(&self) -> &'static [&'static str] {
&["li"]
}
fn apply(&self, content: &str, element: &ElementRef<'_>, ctx: &mut Context<'_>) -> Action {
let node_id = element.id();
let Some(meta) = ctx.list_metadata(node_id) else {
let trimmed = content.trim();
return Action::Replace(format!("- {trimmed}\n"));
};
let continuation_indent = " ".repeat(meta.prefix_width());
let trimmed = content.trim();
let mut result = String::with_capacity(trimmed.len() + meta.prefix().len() + 8);
for (i, line) in trimmed.lines().enumerate() {
if i == 0 {
result.push_str(meta.prefix());
result.push_str(line.trim_start());
continue;
}
result.push('\n');
result.push_str(&continuation_indent);
if !line.trim().is_empty() {
result.push_str(line);
}
}
result.push('\n');
Action::Replace(result)
}
}