use serde::{Deserialize, Serialize}; use uuid::Uuid; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RichTextBlock { pub block_type: BlockType, pub content: String, pub attributes: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum BlockType { Text, Code, Quote, Link, Mention, Emoji, Image, } pub struct RichTextRenderer; impl RichTextRenderer { pub fn new() -> Self { Self {} } pub fn parse_markdown(&self, content: &str) -> Vec { vec![RichTextBlock { block_type: BlockType::Text, content: content.to_string(), attributes: None, }] } pub fn parse_mentions(&self, content: &str) -> Vec { content .split_whitespace() .filter(|w| w.starts_with('@')) .filter_map(|w| Uuid::parse_str(&w[1..]).ok()) .collect() } pub fn highlight_code(&self, code: &str, language: &str) -> String { format!("```{}\n{}\n```", language, code) } pub fn render_to_html(&self, blocks: &[RichTextBlock]) -> String { blocks .iter() .map(|block| match block.block_type { BlockType::Text => { format!("

{}

", html_escape(&block.content)) } BlockType::Code => format!( "
{}
", html_escape(&block.content) ), BlockType::Quote => format!( "
{}
", html_escape(&block.content) ), BlockType::Link => { let safe_href = sanitize_uri(&block.content); format!( "{}", html_escape(&safe_href), html_escape(&block.content) ) } BlockType::Mention => format!( "@{}", html_escape(&block.content) ), BlockType::Emoji => format!( "{}", html_escape(&block.content) ), BlockType::Image => { let safe_src = sanitize_uri(&block.content); if safe_src.is_empty() { String::new() } else { format!("", html_escape(&safe_src)) } } }) .collect::>() .join("\n") } } fn sanitize_uri(uri: &str) -> String { let lower = uri.to_lowercase(); if lower.starts_with("http://") || lower.starts_with("https://") || lower.starts_with("mailto:") { uri.to_string() } else { String::new() } } fn html_escape(s: &str) -> String { s.replace('&', "&") .replace('<', "<") .replace('>', ">") .replace('"', """) .replace('\'', "'") }