use emojis::Emoji; use markdown_it::parser::inline::{InlineRule, InlineState}; use markdown_it::{Node, NodeValue, Renderer}; use maud::{Markup, PreEscaped, Render}; pub struct Markdown<'a>(pub &'a str); impl Render for Markdown<'_> { fn render(&self) -> Markup { // TODO: init once let md = &mut markdown_it::MarkdownIt::new(); markdown_it::plugins::cmark::add(md); markdown_it::plugins::html::add(md); md.inline.add_rule::(); md.inline.add_rule::(); let md = md.parse(&self.0); let html = md.render(); return PreEscaped(html); } } // // MARK: extensions // #[derive(Debug)] pub struct InlineEmote(&'static Emoji); impl NodeValue for InlineEmote { fn render(&self, _node: &Node, fmt: &mut dyn Renderer) { fmt.text(self.0.as_str()); } } impl InlineRule for InlineEmote { const MARKER: char = ':'; fn run(state: &mut InlineState<'_, '_>) -> Option<(Node, usize)> { let input = &state.src[state.pos..state.pos_max]; if !input.starts_with(':') { return None; } let end_idx = input[1..].find(':')? + 1; let emote = emojis::get_by_shortcode(&input[1..end_idx])?; Some((Node::new(InlineEmote(emote)), end_idx + 1)) } } #[derive(Debug)] pub struct InlineMdx(String); impl NodeValue for InlineMdx { fn render(&self, node: &Node, fmt: &mut dyn Renderer) { if mdx_style(&self.0, node, fmt) { return; } fmt.open("code", &[]); fmt.text(&self.0); fmt.close("code"); } } impl InlineRule for InlineMdx { const MARKER: char = '{'; fn run(state: &mut InlineState<'_, '_>) -> Option<(Node, usize)> { let input = &state.src[state.pos..state.pos_max]; if !input.starts_with('{') { return None; } let mut balance = 1; let mut end = 1; for i in input[1..].bytes() { match i { b'}' => balance -= 1, b'{' => balance += 1, _ => {} } if balance == 0 { break; } end += 1; } if balance != 0 { return None; } let content = &input[1..end]; Some((Node::new(InlineMdx(content.to_owned())), content.len() + 2)) } } fn mdx_style(mdx: &str, _node: &Node, fmt: &mut dyn Renderer) -> bool { let mdx = mdx.trim(); if !mdx.starts_with("style(") { return false; } let skip = 6; let mut balance = 1; let mut end = skip; for i in mdx[skip..].bytes() { match i { b')' => balance -= 1, b'(' => balance += 1, _ => {} } if balance == 0 { break; } end += 1; } if balance != 0 { return false; } let style = &mdx[skip..end].trim(); let content = &mdx[end + 1..].trim(); let mut style_str = String::new(); for kv in style.split(";") { let mut s = kv.split(":"); let k = s.next(); let v = s.next(); if k.is_none() || v.is_none() || s.next().is_some() { return false; } let k = k.unwrap().trim(); let v = v.unwrap().trim(); match k { "color" => { style_str.push_str("color:"); style_str.push_str(v); style_str.push_str(";"); } "color_var" => { style_str.push_str("color:var(--"); style_str.push_str(v); style_str.push_str(");"); } _ => continue, } } fmt.open("span", &[("style", style_str)]); fmt.text(&content); fmt.close("span"); return true; }