1use pulldown_cmark::{Alignment, CodeBlockKind, Event, Options, Parser, Tag};
4use yew::virtual_dom::{VNode, VTag, VText};
5use yew::{html, Classes, Html};
6
7fn add_class(vtag: &mut VTag, class: impl Into<Classes>) {
14 let mut classes: Classes = vtag
15 .attributes
16 .iter()
17 .find(|(k, _)| *k == "class")
18 .map(|(_, v)| Classes::from(v.to_owned()))
19 .unwrap_or_default();
20 classes.push(class);
21 vtag.add_attribute("class", classes.to_string());
22}
23
24pub fn render(src: &str) -> Html {
27 let mut elems = vec![];
28 let mut spine = vec![];
29
30 macro_rules! add_child {
31 ($child:expr) => {{
32 let l = spine.len();
33 assert_ne!(l, 0);
34 spine[l - 1].add_child($child);
35 }};
36 }
37
38 let mut options = Options::empty();
39 options.insert(Options::ENABLE_TABLES);
40
41 for ev in Parser::new_ext(src, options) {
42 match ev {
43 Event::Start(tag) => {
44 spine.push(make_tag(tag));
45 }
46 Event::End(tag) => {
47 let l = spine.len();
49 assert!(l >= 1);
50 let mut top = spine.pop().unwrap();
51 if let Tag::CodeBlock(_) = tag {
52 let mut pre = VTag::new("pre");
53 add_class(&mut pre, "bg-secondary text-white p-2");
54 pre.add_child(top.into());
55 top = pre;
56 } else if let Tag::Table(aligns) = tag {
57 for r in top.children.iter_mut() {
58 if let VNode::VTag(ref mut vtag) = r {
59 for (i, c) in vtag.children.iter_mut().enumerate() {
60 if let VNode::VTag(ref mut vtag) = c {
61 match aligns[i] {
62 Alignment::None => {}
63 Alignment::Left => add_class(vtag, "text-left"),
64 Alignment::Center => add_class(vtag, "text-center"),
65 Alignment::Right => add_class(vtag, "text-right"),
66 }
67 }
68 }
69 }
70 }
71 } else if let Tag::TableHead = tag {
72 for c in top.children.iter_mut() {
73 if let VNode::VTag(ref mut vtag) = c {
74 vtag.add_attribute("scope", "col");
77 }
78 }
79 }
80 if l == 1 {
81 elems.push(top);
82 } else {
83 spine[l - 2].add_child(top.into());
84 }
85 }
86 Event::Text(text) => add_child!(VText::new(text.to_string()).into()),
87 Event::Code(code) => {
88 let mut tag = VTag::new("code");
89 tag.add_child(VText::new(code.to_string()).into());
90 add_child!(tag.into());
91 }
92 Event::Rule => add_child!(VTag::new("hr").into()),
93 Event::SoftBreak => add_child!(VText::new("\n").into()),
94 Event::HardBreak => add_child!(VTag::new("br").into()),
95 _ => println!("Unknown event: {:#?}", ev),
96 }
97 }
98
99 if elems.len() == 1 {
100 VNode::VTag(Box::new(elems.pop().unwrap()))
101 } else {
102 html! {
103 <div>{ for elems.into_iter() }</div>
104 }
105 }
106}
107
108fn make_tag(t: Tag) -> VTag {
109 match t {
110 Tag::Paragraph => VTag::new("p"),
111 Tag::Heading(n) => {
112 assert!(n > 0);
113 assert!(n < 7);
114 VTag::new(format!("h{}", n))
115 }
116 Tag::BlockQuote => {
117 let mut el = VTag::new("blockquote");
118 el.add_attribute("class", "blockquote");
119 el
120 }
121 Tag::CodeBlock(code_block_kind) => {
122 let mut el = VTag::new("code");
123
124 if let CodeBlockKind::Fenced(lang) = code_block_kind {
125 match lang.as_ref() {
130 "html" => el.add_attribute("class", "html-language"),
131 "rust" => el.add_attribute("class", "rust-language"),
132 "java" => el.add_attribute("class", "java-language"),
133 "c" => el.add_attribute("class", "c-language"),
134 _ => {} };
136 }
137
138 el
139 }
140 Tag::List(None) => VTag::new("ul"),
141 Tag::List(Some(1)) => VTag::new("ol"),
142 Tag::List(Some(ref start)) => {
143 let mut el = VTag::new("ol");
144 el.add_attribute("start", start.to_string());
145 el
146 }
147 Tag::Item => VTag::new("li"),
148 Tag::Table(_) => {
149 let mut el = VTag::new("table");
150 el.add_attribute("class", "table");
151 el
152 }
153 Tag::TableHead => VTag::new("th"),
154 Tag::TableRow => VTag::new("tr"),
155 Tag::TableCell => VTag::new("td"),
156 Tag::Emphasis => {
157 let mut el = VTag::new("span");
158 el.add_attribute("class", "fst-italic");
159 el
160 }
161 Tag::Strong => {
162 let mut el = VTag::new("span");
163 el.add_attribute("class", "fw-bold");
164 el
165 }
166 Tag::Link(_link_type, ref href, ref title) => {
167 let mut el = VTag::new("a");
168 el.add_attribute("href", href.to_string());
169 let title = title.clone().into_string();
170 if !title.is_empty() {
171 el.add_attribute("title", title);
172 }
173 el
174 }
175 Tag::Image(_link_type, ref src, ref title) => {
176 let mut el = VTag::new("img");
177 el.add_attribute("src", src.to_string());
178 let title = title.clone().into_string();
179 if !title.is_empty() {
180 el.add_attribute("title", title);
181 }
182 el
183 }
184 Tag::FootnoteDefinition(ref _footnote_id) => VTag::new("span"), Tag::Strikethrough => {
186 let mut el = VTag::new("span");
187 el.add_attribute("class", "text-decoration-strikethrough");
188 el
189 }
190 }
191}