113 lines
3.2 KiB
Rust
113 lines
3.2 KiB
Rust
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<serde_json::Value>,
|
|
}
|
|
|
|
#[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<RichTextBlock> {
|
|
vec![RichTextBlock {
|
|
block_type: BlockType::Text,
|
|
content: content.to_string(),
|
|
attributes: None,
|
|
}]
|
|
}
|
|
|
|
pub fn parse_mentions(&self, content: &str) -> Vec<Uuid> {
|
|
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!("<p>{}</p>", html_escape(&block.content))
|
|
}
|
|
BlockType::Code => format!(
|
|
"<pre><code>{}</code></pre>",
|
|
html_escape(&block.content)
|
|
),
|
|
BlockType::Quote => format!(
|
|
"<blockquote>{}</blockquote>",
|
|
html_escape(&block.content)
|
|
),
|
|
BlockType::Link => {
|
|
let safe_href = sanitize_uri(&block.content);
|
|
format!(
|
|
"<a href=\"{}\">{}</a>",
|
|
html_escape(&safe_href),
|
|
html_escape(&block.content)
|
|
)
|
|
}
|
|
BlockType::Mention => format!(
|
|
"<span class=\"mention\">@{}</span>",
|
|
html_escape(&block.content)
|
|
),
|
|
BlockType::Emoji => format!(
|
|
"<span class=\"emoji\">{}</span>",
|
|
html_escape(&block.content)
|
|
),
|
|
BlockType::Image => {
|
|
let safe_src = sanitize_uri(&block.content);
|
|
if safe_src.is_empty() {
|
|
String::new()
|
|
} else {
|
|
format!("<img src=\"{}\" />", html_escape(&safe_src))
|
|
}
|
|
}
|
|
})
|
|
.collect::<Vec<_>>()
|
|
.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('\'', "'")
|
|
}
|