1use ammonia::Builder;
2use markdown::{to_html_with_options, Options, CompileOptions, ParseOptions, Constructs};
3use std::collections::HashSet;
4
5pub fn render_markdown(input: &str) -> String {
7 let options = Options {
8 compile: CompileOptions {
9 allow_any_img_src: false,
10 allow_dangerous_html: true,
11 gfm_task_list_item_checkable: false,
12 gfm_tagfilter: false,
13 ..Default::default()
14 },
15 parse: ParseOptions {
16 constructs: Constructs {
17 gfm_autolink_literal: true,
18 ..Default::default()
19 },
20 gfm_strikethrough_single_tilde: false,
21 math_text_single_dollar: false,
22 mdx_expression_parse: None,
23 mdx_esm_parse: None,
24 ..Default::default()
25 },
26 };
27
28 let html = match to_html_with_options(input, &options) {
29 Ok(h) => h,
30 Err(e) => e.to_string(),
31 };
32
33 let mut allowed_attributes = HashSet::new();
34 allowed_attributes.insert("id");
35 allowed_attributes.insert("class");
36 allowed_attributes.insert("ref");
37 allowed_attributes.insert("aria-label");
38 allowed_attributes.insert("lang");
39 allowed_attributes.insert("title");
40 allowed_attributes.insert("align");
41 allowed_attributes.insert("src");
42
43 Builder::default()
44 .generic_attributes(allowed_attributes)
45 .add_tags(&[
46 "video", "source", "img", "b", "span", "p", "i", "strong", "em", "a",
47 ])
48 .rm_tags(&["script", "style", "link", "canvas"])
49 .add_tag_attributes("a", &["href", "target"])
50 .clean(&html)
51 .to_string()
52 .replace(
53 "src=\"",
54 "loading=\"lazy\" src=\"/api/v0/util/ext/image?img=",
55 )
56 .replace("<video loading=", "<video controls loading=")
57 .replace("-->", "<align class=\"right\">")
58 .replace("->", "<align class=\"center\">")
59 .replace("<-", "</align>")
60}