Skip to content

Commit 61df8bb

Browse files
committed
feat: add support for *non-interactive* checkmarks in markdown
1 parent 0a34496 commit 61df8bb

File tree

1 file changed

+79
-18
lines changed

1 file changed

+79
-18
lines changed

widget/src/markdown.rs

Lines changed: 79 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ use crate::core::theme;
5151
use crate::core::{
5252
self, Color, Element, Length, Padding, Pixels, Theme, color,
5353
};
54-
use crate::{column, container, rich_text, row, rule, scrollable, span, text};
54+
use crate::{
55+
checkbox, column, container, rich_text, row, rule, scrollable, span, text,
56+
};
5557

5658
use std::borrow::BorrowMut;
5759
use std::cell::{Cell, RefCell};
@@ -204,7 +206,7 @@ pub enum Item {
204206
/// The first number of the list, if it is ordered.
205207
start: Option<u64>,
206208
/// The items of the list.
207-
items: Vec<Vec<Item>>,
209+
items: Vec<ListItem>,
208210
},
209211
/// An image.
210212
Image {
@@ -346,6 +348,19 @@ impl Span {
346348
}
347349
}
348350

351+
/// The item of a list.
352+
#[derive(Debug, Clone)]
353+
pub struct ListItem {
354+
checked: Option<bool>,
355+
items: Vec<Item>,
356+
}
357+
358+
impl ListItem {
359+
fn push(&mut self, item: Item) {
360+
self.items.push(item);
361+
}
362+
}
363+
349364
/// Parse the given Markdown content.
350365
///
351366
/// # Example
@@ -499,7 +514,7 @@ fn parse_with<'a>(
499514

500515
struct List {
501516
start: Option<u64>,
502-
items: Vec<Vec<Item>>,
517+
items: Vec<ListItem>,
503518
}
504519

505520
let broken_links = Rc::new(RefCell::new(HashSet::new()));
@@ -525,7 +540,8 @@ fn parse_with<'a>(
525540
pulldown_cmark::Options::ENABLE_YAML_STYLE_METADATA_BLOCKS
526541
| pulldown_cmark::Options::ENABLE_PLUSES_DELIMITED_METADATA_BLOCKS
527542
| pulldown_cmark::Options::ENABLE_TABLES
528-
| pulldown_cmark::Options::ENABLE_STRIKETHROUGH,
543+
| pulldown_cmark::Options::ENABLE_STRIKETHROUGH
544+
| pulldown_cmark::Options::ENABLE_TASKLISTS,
529545
{
530546
let references = state.borrow().references.clone();
531547
let broken_links = broken_links.clone();
@@ -644,7 +660,10 @@ fn parse_with<'a>(
644660
}
645661
pulldown_cmark::Tag::Item => {
646662
if let Some(Scope::List(list)) = stack.last_mut() {
647-
list.items.push(Vec::new());
663+
list.items.push(ListItem {
664+
checked: None,
665+
items: Vec::new(),
666+
});
648667
}
649668

650669
None
@@ -977,6 +996,15 @@ fn parse_with<'a>(
977996
pulldown_cmark::Event::Rule => {
978997
produce(state.borrow_mut(), &mut stack, Item::Rule, source)
979998
}
999+
pulldown_cmark::Event::TaskListMarker(checked) => {
1000+
if let Some(Scope::List(list)) = stack.last_mut() {
1001+
if let Some(item) = list.items.last_mut() {
1002+
item.checked = Some(checked);
1003+
}
1004+
}
1005+
1006+
None
1007+
}
9801008
_ => None,
9811009
})
9821010
}
@@ -1278,18 +1306,32 @@ where
12781306
pub fn unordered_list<'a, Message, Theme, Renderer>(
12791307
viewer: &impl Viewer<'a, Message, Theme, Renderer>,
12801308
settings: Settings,
1281-
items: &'a [Vec<Item>],
1309+
items: &'a [ListItem],
12821310
) -> Element<'a, Message, Theme, Renderer>
12831311
where
12841312
Message: 'a,
12851313
Theme: Catalog + 'a,
12861314
Renderer: core::text::Renderer<Font = Font> + 'a,
12871315
{
1288-
column(items.iter().map(|items| {
1316+
column(items.iter().map(|list_item| {
12891317
row![
1290-
text("•").size(settings.text_size),
1318+
list_item
1319+
.checked
1320+
.map(|is_checked| {
1321+
Element::from(
1322+
checkbox("", is_checked)
1323+
.spacing(0)
1324+
.text_size(settings.text_size),
1325+
)
1326+
})
1327+
.unwrap_or(Element::from(
1328+
text("•")
1329+
.width(settings.text_size)
1330+
.center()
1331+
.size(settings.text_size)
1332+
)),
12911333
view_with(
1292-
items,
1334+
&list_item.items,
12931335
Settings {
12941336
spacing: settings.spacing * 0.6,
12951337
..settings
@@ -1311,7 +1353,7 @@ pub fn ordered_list<'a, Message, Theme, Renderer>(
13111353
viewer: &impl Viewer<'a, Message, Theme, Renderer>,
13121354
settings: Settings,
13131355
start: u64,
1314-
items: &'a [Vec<Item>],
1356+
items: &'a [ListItem],
13151357
) -> Element<'a, Message, Theme, Renderer>
13161358
where
13171359
Message: 'a,
@@ -1320,14 +1362,32 @@ where
13201362
{
13211363
let digits = ((start + items.len() as u64).max(1) as f32).log10().ceil();
13221364

1323-
column(items.iter().enumerate().map(|(i, items)| {
1365+
column(items.iter().enumerate().map(|(i, list_item)| {
13241366
row![
1325-
text!("{}.", i as u64 + start)
1326-
.size(settings.text_size)
1327-
.align_x(alignment::Horizontal::Right)
1328-
.width(settings.text_size * ((digits / 2.0).ceil() + 1.0)),
1367+
list_item
1368+
.checked
1369+
.map(|is_checked| {
1370+
Element::from(
1371+
container(
1372+
checkbox("", is_checked)
1373+
.spacing(0)
1374+
.text_size(settings.text_size),
1375+
)
1376+
.align_right(
1377+
settings.text_size * ((digits / 2.0).ceil() + 1.0),
1378+
),
1379+
)
1380+
})
1381+
.unwrap_or(Element::from(
1382+
text!("{}.", i as u64 + start)
1383+
.size(settings.text_size)
1384+
.align_x(alignment::Horizontal::Right)
1385+
.width(
1386+
settings.text_size * ((digits / 2.0).ceil() + 1.0)
1387+
),
1388+
)),
13291389
view_with(
1330-
items,
1390+
&list_item.items,
13311391
Settings {
13321392
spacing: settings.spacing * 0.6,
13331393
..settings
@@ -1566,7 +1626,7 @@ where
15661626
fn unordered_list(
15671627
&self,
15681628
settings: Settings,
1569-
items: &'a [Vec<Item>],
1629+
items: &'a [ListItem],
15701630
) -> Element<'a, Message, Theme, Renderer> {
15711631
unordered_list(self, settings, items)
15721632
}
@@ -1578,7 +1638,7 @@ where
15781638
&self,
15791639
settings: Settings,
15801640
start: u64,
1581-
items: &'a [Vec<Item>],
1641+
items: &'a [ListItem],
15821642
) -> Element<'a, Message, Theme, Renderer> {
15831643
ordered_list(self, settings, start, items)
15841644
}
@@ -1636,6 +1696,7 @@ pub trait Catalog:
16361696
+ scrollable::Catalog
16371697
+ text::Catalog
16381698
+ crate::rule::Catalog
1699+
+ checkbox::Catalog
16391700
+ crate::table::Catalog
16401701
{
16411702
/// The styling class of a Markdown code block.

0 commit comments

Comments
 (0)