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}