Implement Quote support in markdown widget

This commit is contained in:
Héctor Ramón Jiménez 2025-06-27 16:47:54 +02:00
parent 5ad08acd97
commit ca1bf717b3
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
3 changed files with 91 additions and 17 deletions

View file

@ -400,9 +400,9 @@ where
Renderer: core::Renderer + 'a,
{
fn from(
column: Container<'a, Message, Theme, Renderer>,
container: Container<'a, Message, Theme, Renderer>,
) -> Element<'a, Message, Theme, Renderer> {
Element::new(column)
Element::new(container)
}
}

View file

@ -50,7 +50,10 @@ use crate::core::theme;
use crate::core::{
self, Color, Element, Length, Padding, Pixels, Theme, color,
};
use crate::{column, container, rich_text, row, scrollable, span, text};
use crate::{
column, container, rich_text, row, rule, scrollable, span, text,
vertical_rule,
};
use std::borrow::BorrowMut;
use std::cell::{Cell, RefCell};
@ -208,6 +211,8 @@ pub enum Item {
/// The alternative text of the image.
alt: Text,
},
/// A quote.
Quote(Vec<Item>),
}
/// A bunch of parsed Markdown text.
@ -454,6 +459,7 @@ fn parse_with<'a>(
) -> impl Iterator<Item = (Item, &'a str, HashSet<String>)> + 'a {
enum Scope {
List(List),
Quote(Vec<Item>),
}
struct List {
@ -524,6 +530,9 @@ fn parse_with<'a>(
Scope::List(list) => {
list.items.last_mut().expect("item context").push(item);
}
Scope::Quote(items) => {
items.push(item);
}
}
None
@ -605,6 +614,22 @@ fn parse_with<'a>(
None
}
pulldown_cmark::Tag::BlockQuote(_kind) if !metadata && !table => {
let prev = if spans.is_empty() {
None
} else {
produce(
state.borrow_mut(),
&mut stack,
Item::Paragraph(Text::new(spans.drain(..).collect())),
source,
)
};
stack.push(Scope::Quote(Vec::new()));
prev
}
pulldown_cmark::Tag::CodeBlock(
pulldown_cmark::CodeBlockKind::Fenced(language),
) if !metadata && !table => {
@ -703,7 +728,9 @@ fn parse_with<'a>(
pulldown_cmark::TagEnd::List(_) if !metadata && !table => {
let scope = stack.pop()?;
let Scope::List(list) = scope;
let Scope::List(list) = scope else {
return None;
};
produce(
state.borrow_mut(),
@ -715,6 +742,22 @@ fn parse_with<'a>(
source,
)
}
pulldown_cmark::TagEnd::BlockQuote(_kind)
if !metadata && !table =>
{
let scope = stack.pop()?;
let Scope::Quote(quote) = scope else {
return None;
};
produce(
state.borrow_mut(),
&mut stack,
Item::Quote(quote),
source,
)
}
pulldown_cmark::TagEnd::Image if !metadata && !table => {
let (url, title) = image.take()?;
let alt = Text::new(spans.drain(..).collect());
@ -1063,6 +1106,7 @@ where
start: Some(start),
items,
} => viewer.ordered_list(settings, *start, items),
Item::Quote(quote) => viewer.quote(settings, quote),
}
}
@ -1226,7 +1270,33 @@ where
.into()
}
/// A view strategy to display a Markdown [`Item`].j
/// Displays a quote using the default look.
pub fn quote<'a, Message, Theme, Renderer>(
viewer: &impl Viewer<'a, Message, Theme, Renderer>,
settings: Settings,
contents: &'a [Item],
) -> Element<'a, Message, Theme, Renderer>
where
Message: 'a,
Theme: Catalog + 'a,
Renderer: core::text::Renderer<Font = Font> + 'a,
{
row![
vertical_rule(4),
column(
contents
.iter()
.enumerate()
.map(|(i, content)| item(viewer, settings, content, i)),
)
.spacing(settings.spacing.0),
]
.height(Length::Shrink)
.spacing(settings.spacing.0)
.into()
}
/// A view strategy to display a Markdown [`Item`].
pub trait Viewer<'a, Message, Theme = crate::Theme, Renderer = crate::Renderer>
where
Self: Sized + 'a,
@ -1321,6 +1391,17 @@ where
) -> Element<'a, Message, Theme, Renderer> {
ordered_list(self, settings, start, items)
}
/// Displays a quote.
///
/// By default, it call [`quote`].
fn quote(
&self,
settings: Settings,
contents: &'a [Item],
) -> Element<'a, Message, Theme, Renderer> {
quote(self, settings, contents)
}
}
#[derive(Debug, Clone, Copy)]
@ -1338,7 +1419,7 @@ where
/// The theme catalog of Markdown items.
pub trait Catalog:
container::Catalog + scrollable::Catalog + text::Catalog
container::Catalog + scrollable::Catalog + rule::Catalog + text::Catalog
{
/// The styling class of a Markdown code block.
fn code_block<'a>() -> <Self as container::Catalog>::Class<'a>;

View file

@ -134,9 +134,7 @@ where
let style = theme.style(&self.class);
let bounds = if self.is_horizontal {
let line_y = (bounds.y + (bounds.height / 2.0)
- (style.width as f32 / 2.0))
.round();
let line_y = (bounds.y + (bounds.height / 2.0)).round();
let (offset, line_width) = style.fill_mode.fill(bounds.width);
let line_x = bounds.x + offset;
@ -145,12 +143,10 @@ where
x: line_x,
y: line_y,
width: line_width,
height: style.width as f32,
height: bounds.height,
}
} else {
let line_x = (bounds.x + (bounds.width / 2.0)
- (style.width as f32 / 2.0))
.round();
let line_x = (bounds.x + (bounds.width / 2.0)).round();
let (offset, line_height) = style.fill_mode.fill(bounds.height);
let line_y = bounds.y + offset;
@ -158,7 +154,7 @@ where
Rectangle {
x: line_x,
y: line_y,
width: style.width as f32,
width: bounds.width,
height: line_height,
}
};
@ -192,8 +188,6 @@ where
pub struct Style {
/// The color of the rule.
pub color: Color,
/// The width (thickness) of the rule line.
pub width: u16,
/// The radius of the line corners.
pub radius: border::Radius,
/// The [`FillMode`] of the rule.
@ -301,7 +295,6 @@ pub fn default(theme: &Theme) -> Style {
Style {
color: palette.background.strong.color,
width: 1,
radius: 0.0.into(),
fill_mode: FillMode::Full,
snap: true,