text_view: Fix list item to merge inline paragraphs, and fix paragraphs margin. (#1036)
## Before <img width="1712" alt="image" src="https://github.com/user-attachments/assets/579385e6-a206-45e4-80a1-e1e2cdd6b164" /> ## After <img width="1712" alt="image" src="https://github.com/user-attachments/assets/2f551d22-5662-4c3c-a0e5-5ce9f26e1e66" />
This commit is contained in:
parent
9eb7e10a1d
commit
4075447c63
3 changed files with 91 additions and 57 deletions
|
|
@ -201,6 +201,25 @@ impl Paragraph {
|
|||
Self::Image { .. } => 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to merge two paragraphs, if they are both text elements.
|
||||
///
|
||||
/// - Returns `true` if other have merge into self.
|
||||
/// - Returns `false` if not able to merge.
|
||||
pub fn try_merge(&mut self, other: &Self) -> bool {
|
||||
if let Self::Texts { children, .. } = self {
|
||||
if let Self::Texts {
|
||||
children: other_children,
|
||||
..
|
||||
} = other
|
||||
{
|
||||
children.extend(other_children.clone());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
|
@ -262,15 +281,10 @@ pub enum Node {
|
|||
html: bool,
|
||||
},
|
||||
Divider,
|
||||
Ignore,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub(super) fn is_ignore(&self) -> bool {
|
||||
matches!(self, Self::Ignore)
|
||||
}
|
||||
|
||||
pub(super) fn is_list_item(&self) -> bool {
|
||||
matches!(self, Self::ListItem { .. })
|
||||
}
|
||||
|
|
@ -283,11 +297,7 @@ impl Node {
|
|||
pub(super) fn compact(&self) -> Node {
|
||||
match self {
|
||||
Self::Root { children } => {
|
||||
let children = children
|
||||
.iter()
|
||||
.map(|c| c.compact())
|
||||
.filter(|c| !c.is_ignore())
|
||||
.collect::<Vec<_>>();
|
||||
let children = children.iter().map(|c| c.compact()).collect::<Vec<_>>();
|
||||
if children.len() == 1 {
|
||||
children.first().unwrap().compact()
|
||||
} else {
|
||||
|
|
@ -310,7 +320,13 @@ impl RenderOnce for Paragraph {
|
|||
|
||||
for text_node in children.into_iter() {
|
||||
let text_len = text_node.text.len();
|
||||
text.push_str(&text_node.text);
|
||||
let part = if text.len() == 0 {
|
||||
// trim start for first text
|
||||
text_node.text.trim_start()
|
||||
} else {
|
||||
text_node.text.as_str()
|
||||
};
|
||||
text.push_str(&part);
|
||||
|
||||
let mut node_highlights = vec![];
|
||||
for (range, style) in text_node.marks {
|
||||
|
|
@ -421,6 +437,7 @@ impl Node {
|
|||
ordered: state.ordered,
|
||||
todo: checked.is_some(),
|
||||
}),
|
||||
false,
|
||||
true,
|
||||
text_view_style,
|
||||
window,
|
||||
|
|
@ -485,6 +502,7 @@ impl Node {
|
|||
todo: checked.is_some(),
|
||||
}),
|
||||
true,
|
||||
true,
|
||||
text_view_style,
|
||||
window,
|
||||
cx,
|
||||
|
|
@ -607,6 +625,7 @@ impl Node {
|
|||
pub(crate) fn render(
|
||||
self,
|
||||
list_state: Option<ListState>,
|
||||
is_root: bool,
|
||||
is_last_child: bool,
|
||||
style: &TextViewStyle,
|
||||
window: &mut Window,
|
||||
|
|
@ -624,8 +643,8 @@ impl Node {
|
|||
.children({
|
||||
let children_len = children.len();
|
||||
children.into_iter().enumerate().map(move |(index, c)| {
|
||||
let is_last_child = index == children_len - 1;
|
||||
c.render(None, is_last_child, style, window, cx)
|
||||
let is_last_child = is_root && index == children_len - 1;
|
||||
c.render(None, false, is_last_child, style, window, cx)
|
||||
})
|
||||
})
|
||||
.into_any_element(),
|
||||
|
|
@ -699,10 +718,9 @@ impl Node {
|
|||
.mb(mb)
|
||||
.into_any_element(),
|
||||
Node::Break { .. } => div().into_any_element(),
|
||||
Node::Ignore => div().into_any_element(),
|
||||
_ => {
|
||||
if cfg!(debug_assertions) {
|
||||
eprintln!("Unknown implementation: {:?}", self);
|
||||
tracing::warn!("unknown implementation: {:?}", self);
|
||||
}
|
||||
|
||||
div().into_any_element()
|
||||
|
|
@ -869,7 +887,6 @@ impl Node {
|
|||
}
|
||||
}
|
||||
Node::Divider => "---".to_string(),
|
||||
Node::Ignore => "".to_string(),
|
||||
Node::Unknown => "".to_string(),
|
||||
}
|
||||
.trim()
|
||||
|
|
|
|||
|
|
@ -75,7 +75,8 @@ pub(super) fn parse_html(source: &str) -> Result<element::Node, SharedString> {
|
|||
|
||||
let mut paragraph = Paragraph::default();
|
||||
// NOTE: The outer paragraph is not used.
|
||||
let node: element::Node = parse_node(&dom.document, &mut paragraph);
|
||||
let node: element::Node =
|
||||
parse_node(&dom.document, &mut paragraph).unwrap_or(element::Node::Unknown);
|
||||
let node = node.compact();
|
||||
|
||||
Ok(node)
|
||||
|
|
@ -173,7 +174,7 @@ impl Element for HtmlElement {
|
|||
|
||||
let mut el = div()
|
||||
.map(|this| match root {
|
||||
Ok(node) => this.child(node.render(None, true, &self.style, window, cx)),
|
||||
Ok(node) => this.child(node.render(None, true, true, &self.style, window, cx)),
|
||||
Err(err) => this.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
|
|
@ -492,7 +493,7 @@ fn parse_paragraph(
|
|||
local_name!("img") => {
|
||||
let Some(src) = attr_value(attrs, local_name!("src")) else {
|
||||
if cfg!(debug_assertions) {
|
||||
eprintln!("[html] Image node missing src attribute");
|
||||
tracing::warn!("Image node missing src attribute");
|
||||
}
|
||||
return (text, marks);
|
||||
};
|
||||
|
|
@ -509,7 +510,6 @@ fn parse_paragraph(
|
|||
title: title.map(Into::into),
|
||||
});
|
||||
}
|
||||
|
||||
_ => {
|
||||
// All unknown tags to as text
|
||||
let mut child_paragraph = Paragraph::default();
|
||||
|
|
@ -539,7 +539,7 @@ fn parse_paragraph(
|
|||
(text, marks)
|
||||
}
|
||||
|
||||
fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> element::Node {
|
||||
fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> Option<element::Node> {
|
||||
match node.data {
|
||||
NodeData::Text { ref contents } => {
|
||||
let text = contents.borrow().to_string();
|
||||
|
|
@ -547,14 +547,14 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> element::Node {
|
|||
paragraph.push_str(&text);
|
||||
}
|
||||
|
||||
element::Node::Ignore
|
||||
None
|
||||
}
|
||||
NodeData::Element {
|
||||
ref name,
|
||||
ref attrs,
|
||||
..
|
||||
} => match name.local {
|
||||
local_name!("br") => element::Node::Break { html: true },
|
||||
local_name!("br") => Some(element::Node::Break { html: true }),
|
||||
local_name!("h1")
|
||||
| local_name!("h2")
|
||||
| local_name!("h3")
|
||||
|
|
@ -587,9 +587,9 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> element::Node {
|
|||
if children.len() > 0 {
|
||||
children.push(heading);
|
||||
|
||||
element::Node::Root { children }
|
||||
Some(element::Node::Root { children })
|
||||
} else {
|
||||
heading
|
||||
Some(heading)
|
||||
}
|
||||
}
|
||||
local_name!("img") => {
|
||||
|
|
@ -601,9 +601,9 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> element::Node {
|
|||
|
||||
let Some(src) = attr_value(attrs, local_name!("src")) else {
|
||||
if cfg!(debug_assertions) {
|
||||
eprintln!("[html] Image node missing src attribute");
|
||||
tracing::warn!("image node missing src attribute");
|
||||
}
|
||||
return element::Node::Ignore;
|
||||
return None;
|
||||
};
|
||||
|
||||
let alt = attr_value(&attrs, local_name!("alt"));
|
||||
|
|
@ -623,9 +623,9 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> element::Node {
|
|||
|
||||
if children.len() > 0 {
|
||||
children.push(element::Node::Paragraph(image));
|
||||
element::Node::Root { children }
|
||||
Some(element::Node::Root { children })
|
||||
} else {
|
||||
element::Node::Paragraph(image)
|
||||
Some(element::Node::Paragraph(image))
|
||||
}
|
||||
}
|
||||
local_name!("ul") | local_name!("ol") => {
|
||||
|
|
@ -641,7 +641,9 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> element::Node {
|
|||
|
||||
for child in node.children.borrow().iter() {
|
||||
let mut child_paragraph = Paragraph::default();
|
||||
list_children.push(parse_node(child, &mut child_paragraph));
|
||||
if let Some(child_node) = parse_node(child, &mut child_paragraph) {
|
||||
list_children.push(child_node);
|
||||
}
|
||||
}
|
||||
|
||||
let list = element::Node::List {
|
||||
|
|
@ -650,19 +652,30 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> element::Node {
|
|||
};
|
||||
if children.len() > 0 {
|
||||
children.push(list);
|
||||
element::Node::Root { children }
|
||||
Some(element::Node::Root { children })
|
||||
} else {
|
||||
list
|
||||
Some(list)
|
||||
}
|
||||
}
|
||||
local_name!("li") => {
|
||||
let mut children = vec![];
|
||||
|
||||
for child in node.children.borrow().iter() {
|
||||
let mut child_paragraph = Paragraph::default();
|
||||
children.push(parse_node(child, &mut child_paragraph));
|
||||
if let Some(child_node) = parse_node(child, &mut child_paragraph) {
|
||||
children.push(child_node);
|
||||
}
|
||||
if child_paragraph.text_len() > 0 {
|
||||
children.push(element::Node::Paragraph(child_paragraph.clone()));
|
||||
child_paragraph.clear();
|
||||
// If last child is paragraph, merge child
|
||||
if let Some(last_child) = children.last_mut() {
|
||||
if let element::Node::Paragraph(last_paragraph) = last_child {
|
||||
if last_paragraph.try_merge(&child_paragraph) {
|
||||
continue;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
children.push(element::Node::Paragraph(child_paragraph));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -671,11 +684,11 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> element::Node {
|
|||
paragraph.clear();
|
||||
}
|
||||
|
||||
element::Node::ListItem {
|
||||
Some(element::Node::ListItem {
|
||||
children,
|
||||
spread: false,
|
||||
checked: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
local_name!("table") => {
|
||||
let mut children = vec![];
|
||||
|
|
@ -704,9 +717,9 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> element::Node {
|
|||
let table = element::Node::Table(table);
|
||||
if children.len() > 0 {
|
||||
children.push(table);
|
||||
element::Node::Root { children }
|
||||
Some(element::Node::Root { children })
|
||||
} else {
|
||||
table
|
||||
Some(table)
|
||||
}
|
||||
}
|
||||
local_name!("blockquote") => {
|
||||
|
|
@ -725,9 +738,9 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> element::Node {
|
|||
}
|
||||
children.push(element::Node::Blockquote(blockquote));
|
||||
|
||||
element::Node::Root { children: children }
|
||||
Some(element::Node::Root { children: children })
|
||||
}
|
||||
local_name!("style") | local_name!("script") => element::Node::Ignore,
|
||||
local_name!("style") | local_name!("script") => None,
|
||||
_ => {
|
||||
if BLOCK_ELEMENTS.contains(&name.local.trim()) {
|
||||
let mut children: Vec<element::Node> = vec![];
|
||||
|
|
@ -744,7 +757,9 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> element::Node {
|
|||
|
||||
// Inner of the block element -- The "Inner text of block element"
|
||||
for child in node.children.borrow().iter() {
|
||||
children.push(parse_node(child, paragraph));
|
||||
if let Some(child_node) = parse_node(child, paragraph) {
|
||||
children.push(child_node);
|
||||
}
|
||||
}
|
||||
|
||||
// if !paragraph.is_empty() {
|
||||
|
|
@ -753,9 +768,9 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> element::Node {
|
|||
// }
|
||||
|
||||
if children.is_empty() {
|
||||
element::Node::Ignore
|
||||
None
|
||||
} else {
|
||||
element::Node::Root { children }
|
||||
Some(element::Node::Root { children })
|
||||
}
|
||||
} else {
|
||||
// Others to as Inline
|
||||
|
|
@ -764,9 +779,9 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> element::Node {
|
|||
if paragraph.is_image() {
|
||||
let image = paragraph.clone();
|
||||
paragraph.clear();
|
||||
element::Node::Paragraph(image)
|
||||
Some(element::Node::Paragraph(image))
|
||||
} else {
|
||||
element::Node::Ignore
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -774,7 +789,9 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> element::Node {
|
|||
NodeData::Document => {
|
||||
let mut children = vec![];
|
||||
for child in node.children.borrow().iter() {
|
||||
children.push(parse_node(child, paragraph));
|
||||
if let Some(child_node) = parse_node(child, paragraph) {
|
||||
children.push(child_node);
|
||||
}
|
||||
}
|
||||
|
||||
if !paragraph.is_empty() {
|
||||
|
|
@ -782,11 +799,11 @@ fn parse_node(node: &Rc<Node>, paragraph: &mut Paragraph) -> element::Node {
|
|||
paragraph.clear();
|
||||
}
|
||||
|
||||
element::Node::Root { children }
|
||||
Some(element::Node::Root { children })
|
||||
}
|
||||
NodeData::Doctype { .. } => element::Node::Ignore,
|
||||
NodeData::Comment { .. } => element::Node::Ignore,
|
||||
NodeData::ProcessingInstruction { .. } => element::Node::Ignore,
|
||||
NodeData::Doctype { .. }
|
||||
| NodeData::Comment { .. }
|
||||
| NodeData::ProcessingInstruction { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ impl Element for MarkdownElement {
|
|||
|
||||
let mut el = div()
|
||||
.map(|this| match root {
|
||||
Ok(node) => this.child(node.render(None, true, &self.style, window, cx)),
|
||||
Ok(node) => this.child(node.render(None, true, true, &self.style, window, cx)),
|
||||
Err(err) => this.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
|
|
@ -341,13 +341,13 @@ fn parse_paragraph(paragraph: &mut Paragraph, node: &mdast::Node) -> String {
|
|||
});
|
||||
} else {
|
||||
if cfg!(debug_assertions) {
|
||||
eprintln!("[markdown] unsupported inline html tag: {:#?}", el);
|
||||
tracing::warn!("unsupported inline html tag: {:#?}", el);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
if cfg!(debug_assertions) {
|
||||
eprintln!("[markdown] error parsing html: {:#?}", err);
|
||||
tracing::warn!("failed parsing html: {:#?}", err);
|
||||
}
|
||||
|
||||
text.push_str(&val.value);
|
||||
|
|
@ -355,7 +355,7 @@ fn parse_paragraph(paragraph: &mut Paragraph, node: &mdast::Node) -> String {
|
|||
},
|
||||
_ => {
|
||||
if cfg!(debug_assertions) {
|
||||
eprintln!("[markdown] unsupported inline node: {:#?}", node);
|
||||
tracing::warn!("unsupported inline node: {:#?}", node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -437,7 +437,7 @@ fn ast_to_node(value: mdast::Node, style: &TextViewStyle, cx: &mut App) -> eleme
|
|||
Ok(el) => el,
|
||||
Err(err) => {
|
||||
if cfg!(debug_assertions) {
|
||||
eprintln!("[markdown] error parsing html: {:#?}", err);
|
||||
tracing::warn!("error parsing html: {:#?}", err);
|
||||
}
|
||||
|
||||
element::Node::Paragraph(val.value.into())
|
||||
|
|
@ -494,7 +494,7 @@ fn ast_to_node(value: mdast::Node, style: &TextViewStyle, cx: &mut App) -> eleme
|
|||
}
|
||||
_ => {
|
||||
if cfg!(debug_assertions) {
|
||||
eprintln!("[markdown] unsupported node: {:#?}", value);
|
||||
tracing::warn!("unsupported node: {:#?}", value);
|
||||
}
|
||||
element::Node::Unknown
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue