Skip to main content

vizia_core/views/
markdown.rs

1#![cfg(feature = "markdown")]
2
3use std::cell::RefCell;
4
5use comrak::nodes::{Ast, NodeValue};
6use comrak::{Arena, Options, parse_document};
7
8use crate::prelude::*;
9
10/// A view which parses and displays markdown as rich text.
11pub struct Markdown {}
12
13impl Markdown {
14    /// Create a new [Markdown] view.
15    pub fn new<'a>(cx: &'a mut Context, document: &str) -> Handle<'a, Self> {
16        Self {}
17            .build(cx, |cx| {
18                // The returned nodes are created in the supplied Arena, and are bound by its lifetime.
19                let arena = Arena::new();
20
21                let mut options = Options::default();
22                options.extension.strikethrough = true;
23                options.extension.table = true;
24
25                // Parse the document into a root `AstNode`
26                let root = parse_document(&arena, document, &options);
27
28                for node in root.children() {
29                    parse_node(cx, node, 0);
30                }
31            })
32            .height(Auto)
33    }
34}
35
36impl View for Markdown {
37    fn element(&self) -> Option<&'static str> {
38        Some("markdown")
39    }
40}
41
42fn parse_node<'a>(
43    cx: &mut Context,
44    node: &'a comrak::arena_tree::Node<'a, RefCell<Ast>>,
45    list_level: usize,
46) {
47    match &node.data.borrow().value {
48        NodeValue::Paragraph => {
49            Label::rich(cx, "", |cx| {
50                for child in node.children() {
51                    parse_node(cx, child, list_level);
52                }
53            })
54            .class("p");
55        }
56
57        NodeValue::Heading(heading) => {
58            Label::rich(cx, "", |cx| {
59                for child in node.children() {
60                    parse_node(cx, child, list_level);
61                }
62            })
63            .class(match heading.level {
64                1 => "h1",
65                2 => "h2",
66                3 => "h3",
67                4 => "h4",
68                5 => "h5",
69                6 => "h6",
70                _ => "h6",
71            });
72        }
73
74        NodeValue::Text(text) => {
75            TextSpan::new(cx, text, |_| {}).class("span");
76        }
77
78        NodeValue::Emph => {
79            TextSpan::new(cx, "", |cx| {
80                for child in node.children() {
81                    parse_node(cx, child, list_level);
82                }
83            })
84            .class("emph");
85        }
86
87        NodeValue::Strong => {
88            TextSpan::new(cx, "", |cx| {
89                for child in node.children() {
90                    parse_node(cx, child, list_level);
91                }
92            })
93            .class("strong");
94        }
95
96        NodeValue::Strikethrough => {
97            TextSpan::new(cx, "", |cx| {
98                for child in node.children() {
99                    parse_node(cx, child, list_level);
100                }
101            })
102            .class("strikethrough");
103        }
104
105        NodeValue::List(_list) => {
106            VStack::new(cx, |cx| {
107                for child in node.children() {
108                    parse_node(cx, child, list_level);
109                }
110            })
111            .height(Auto)
112            .left(Pixels(20.0));
113        }
114
115        NodeValue::Item(_list) => {
116            HStack::new(cx, |cx| {
117                Label::new(cx, "\u{2022} ").width(Auto);
118                VStack::new(cx, |cx| {
119                    for child in node.children() {
120                        parse_node(cx, child, list_level + 1);
121                    }
122                })
123                .height(Auto);
124            })
125            .class("li")
126            .alignment(Alignment::Left)
127            .height(Auto);
128        }
129
130        NodeValue::Code(code) => {
131            TextSpan::new(cx, &code.literal.to_owned(), |_| {}).class("code");
132        }
133
134        NodeValue::CodeBlock(code_block) => {
135            let mut code = code_block.literal.to_owned();
136            code.pop().unwrap();
137            ScrollView::new(cx, |cx| {
138                Label::new(cx, code).class("code");
139            })
140            .show_vertical_scrollbar(false)
141            .height(Auto)
142            .width(Stretch(1.0));
143        }
144
145        NodeValue::Link(link) => {
146            let url = link.url.clone();
147            TextSpan::new(cx, "", |cx| {
148                for child in node.children() {
149                    parse_node(cx, child, list_level);
150                }
151            })
152            .cursor(CursorIcon::Hand)
153            .pointer_events(PointerEvents::Auto)
154            .on_press(move |_| {
155                open::that(url.as_str()).unwrap();
156            })
157            .class("link");
158        }
159
160        NodeValue::SoftBreak => {
161            TextSpan::new(cx, "\n", |cx| {
162                for child in node.children() {
163                    parse_node(cx, child, list_level);
164                }
165            });
166        }
167
168        NodeValue::Table(_table) => {
169            VStack::new(cx, |cx| {
170                for child in node.children() {
171                    parse_node(cx, child, list_level);
172                }
173            })
174            .class("table")
175            .width(Stretch(1.0))
176            .height(Auto);
177        }
178
179        NodeValue::TableRow(headers) => {
180            HStack::new(cx, |cx| {
181                for child in node.children() {
182                    parse_node(cx, child, list_level);
183                }
184            })
185            .class("table-row")
186            .toggle_class("table-headers", *headers)
187            .width(Stretch(1.0))
188            .height(Auto);
189            Divider::horizontal(cx);
190        }
191
192        NodeValue::TableCell => {
193            Label::rich(cx, "", |cx| {
194                for child in node.children() {
195                    parse_node(cx, child, list_level);
196                }
197            })
198            .class("table-cell")
199            .width(Stretch(1.0));
200        }
201
202        _ => {}
203    }
204}