Skip to main content

mxr_compose/
render.rs

1use comrak::{markdown_to_html, Options};
2
3/// Minimal HTML template wrapping rendered markdown.
4const HTML_TEMPLATE: &str = r#"<!DOCTYPE html>
5<html>
6<head><meta charset="utf-8"></head>
7<body style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 14px; line-height: 1.5; color: #333; max-width: 600px;">
8{content}
9</body>
10</html>"#;
11
12/// Rendered email parts from markdown source.
13pub struct RenderedMessage {
14    /// Raw markdown as text/plain part.
15    pub plain: String,
16    /// Rendered HTML as text/html part.
17    pub html: String,
18}
19
20/// Convert markdown body to multipart-ready text/plain + text/html.
21pub fn render_markdown(markdown: &str) -> RenderedMessage {
22    let mut options = Options::default();
23    options.extension.strikethrough = true;
24    options.extension.table = true;
25    options.extension.autolink = true;
26    options.extension.tasklist = true;
27    options.render.unsafe_ = false;
28
29    let html_content = markdown_to_html(markdown, &options);
30    let html = HTML_TEMPLATE.replace("{content}", &html_content);
31
32    RenderedMessage {
33        plain: markdown.to_string(),
34        html,
35    }
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    #[test]
43    fn render_basic_markdown() {
44        let md = "Hello **world**!";
45        let result = render_markdown(md);
46        assert_eq!(result.plain, md);
47        assert!(result.html.contains("<strong>world</strong>"));
48        assert!(result.html.contains("<!DOCTYPE html>"));
49    }
50
51    #[test]
52    fn render_with_links() {
53        let md = "Check out https://example.com for more.";
54        let result = render_markdown(md);
55        assert!(result.html.contains("href"));
56    }
57
58    #[test]
59    fn render_empty() {
60        let result = render_markdown("");
61        assert!(result.plain.is_empty());
62        assert!(result.html.contains("<!DOCTYPE html>"));
63    }
64}