gitdataai/lib/channel/richtext.rs
2026-05-30 01:38:40 +08:00

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('&', "&amp;")
.replace('<', "&lt;")
.replace('>', "&gt;")
.replace('"', "&quot;")
.replace('\'', "&#x27;")
}