use linkify::{LinkFinder, LinkKind};
pub fn find_links(text: &str) -> Vec<LinkMatch> {
let mut finder = LinkFinder::new();
finder.kinds(&[LinkKind::Url, LinkKind::Email]);
finder
.links(text)
.map(|link| LinkMatch {
start: link.start(),
end: link.end(),
url: link.as_str().to_string(),
kind: match link.kind() {
LinkKind::Url => LinkKindSimple::Url,
LinkKind::Email => LinkKindSimple::Email,
_ => LinkKindSimple::Url,
},
})
.collect()
}
#[derive(Clone, Debug)]
pub struct LinkMatch {
pub start: usize,
pub end: usize,
pub url: String,
pub kind: LinkKindSimple,
}
#[derive(Clone, Debug, PartialEq)]
pub enum LinkKindSimple {
Url,
Email,
}
pub fn linkify_text(text: &str) -> String {
let links = find_links(text);
if links.is_empty() {
return html_escape(text);
}
let mut result = String::new();
let mut last_end = 0;
for link in &links {
result.push_str(&html_escape(&text[last_end..link.start]));
let href = match link.kind {
LinkKindSimple::Url => link.url.clone(),
LinkKindSimple::Email => format!("mailto:{}", link.url),
};
result.push_str(&format!(
r#"<a href="{}" target="_blank" rel="noopener noreferrer">{}</a>"#,
html_escape(&href),
html_escape(&link.url)
));
last_end = link.end;
}
result.push_str(&html_escape(&text[last_end..]));
result
}
fn html_escape(text: &str) -> String {
text.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
.replace('\'', "'")
}