yew_mdx/
lib.rs

1use markdown::mdast::Node;
2use markdown::*;
3use yew::prelude::*;
4
5#[derive(Properties, PartialEq)]
6pub struct MarkdownProps {
7    pub source: String,
8    pub render: Callback<String, Html>,
9}
10
11#[function_component(Markdown)]
12pub fn markdown(props: &MarkdownProps) -> Html {
13    let ast = use_memo(
14        |string| to_mdast(&string, &ParseOptions {
15            constructs: Constructs {
16                gfm_table: true,
17                ..Constructs::gfm()
18            },
19            ..ParseOptions::gfm()
20        }),
21        props.source.clone(),
22    );
23
24    if let Ok(node) = &*ast {
25        let node = node.clone();
26        let render = props.render.clone();
27        html! {
28            <MarkdownNode {node} {render} />
29        }
30    } else {
31        html! {{"Error"}}
32    }
33}
34
35#[derive(Properties, PartialEq)]
36pub struct MarkdownNodesProps {
37    pub nodes: Vec<Node>,
38    pub render: Callback<String, Html>,
39}
40
41#[function_component(MarkdownNodes)]
42pub fn markdown_nodes(props: &MarkdownNodesProps) -> Html {
43    props
44        .nodes
45        .iter()
46        .cloned()
47        .map(|node| html!{ <MarkdownNode {node} render={props.render.clone()} /> })
48        .collect::<Html>()
49}
50
51#[derive(Properties, PartialEq)]
52pub struct MarkdownNodeProps {
53    pub node: Node,
54    pub render: Callback<String, Html>,
55}
56
57#[function_component(MarkdownNode)]
58pub fn markdown_node(props: &MarkdownNodeProps) -> Html {
59    let render = props.render.clone();
60    match &props.node {
61        Node::Root(root) => html!(
62            <div class="markdown">
63                <MarkdownNodes nodes={root.children.clone()} {render} />
64            </div>
65        ),
66        Node::BlockQuote(quote) => {
67            let nodes = quote.children.clone();
68            html!(<blockquote><MarkdownNodes {nodes} {render} /></blockquote>)
69        },
70        Node::List(list) if !list.ordered => {
71            let nodes = list.children.clone();
72            html!(<ul><MarkdownNodes {nodes} {render} /></ul>)
73        },
74        Node::InlineCode(code) => html!(<code>{&code.value}</code>),
75        Node::Delete(delete) => html!(<strike><MarkdownNodes nodes={delete.children.clone()} {render} /></strike>),
76        Node::Heading(heading) => {
77            let nodes = heading.children.clone();
78            match heading.depth {
79                1 => html!{<h1><MarkdownNodes {nodes} {render} /></h1>},
80                2 => html!{<h2><MarkdownNodes {nodes} {render} /></h2>},
81                3 => html!{<h3><MarkdownNodes {nodes} {render} /></h3>},
82                4 => html!{<h4><MarkdownNodes {nodes} {render} /></h4>},
83                5 => html!{<h5><MarkdownNodes {nodes} {render} /></h5>},
84                6 => html!{<h6><MarkdownNodes {nodes} {render} /></h6>},
85                _ => html!({"Unsupported heading"}),
86            }
87        },
88        Node::Text(text) => html!({&text.value}),
89        Node::Paragraph(paragraph) => {
90            let nodes = paragraph.children.clone();
91            html!{<p><MarkdownNodes {nodes} {render} /></p>}
92        },
93        Node::ThematicBreak(_) => html!{<hr />},
94        Node::Emphasis(emphasis) => {
95            let nodes = emphasis.children.clone();
96            html!{<em><MarkdownNodes {nodes} {render} /></em>}
97        },
98        Node::Strong(strong) => {
99            let nodes = strong.children.clone();
100            html!{<strong><MarkdownNodes {nodes} {render} /></strong>}
101        },
102        Node::Code(code) => html!(<pre><code>{&code.value}</code></pre>),
103        Node::Link(link) => html!(<a href={link.url.clone()}><MarkdownNodes nodes={link.children.clone()} {render} /></a>),
104        Node::Break(_) => html!(<br />),
105        Node::List(list) if list.ordered => {
106            let nodes = list.children.clone();
107            html!(<ol><MarkdownNodes {nodes} {render} /></ol>)
108        },
109        Node::ListItem(item) => {
110            let nodes = item.children.clone();
111            html!(<li><MarkdownNodes {nodes} {render} /></li>)
112        },
113        Node::Html(html) => render.emit(html.value.clone()),
114        node => html!{{format!("Error: {node:?}")}},
115    }
116}