orbital_markdown/
mentions.rs1use regex::Regex;
2
3use crate::links::ORBITAL_LINK_INLINE_CLASS;
4use crate::mention_style::MentionLinkStyle;
5
6#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct MentionRef<'a> {
9 pub id: &'a str,
10 pub display_label: &'a str,
11}
12
13pub const MENTION_HREF_PREFIX: &str = "mention-ref:";
15
16pub fn prepare_mention_markdown(markdown: &str) -> String {
18 let Ok(re) = Regex::new(r"@\[([^\]]+)\]\(([^)]+)\)") else {
19 return markdown.to_string();
20 };
21 re.replace_all(markdown, |caps: ®ex::Captures| {
22 let label = caps.get(1).map(|m| m.as_str()).unwrap_or("");
23 let id = caps.get(2).map(|m| m.as_str()).unwrap_or("");
24 format!("[@{label}]({MENTION_HREF_PREFIX}{id})")
25 })
26 .to_string()
27}
28
29pub fn replace_mention_links(html: &str, style: MentionLinkStyle) -> String {
31 let Ok(re) = Regex::new(&format!(
32 r#"(?i)<a href="{prefix}([^"]+)">([^<]*)</a>"#,
33 prefix = regex::escape(MENTION_HREF_PREFIX)
34 )) else {
35 return html.to_string();
36 };
37
38 re.replace_all(html, |caps: ®ex::Captures| {
39 let id = caps.get(1).map(|m| m.as_str()).unwrap_or("");
40 let label = caps.get(2).map(|m| m.as_str()).unwrap_or("");
41 format!(
42 r##"<a href="#mention-{id}" class="{class} {link_class}" data-mention-id="{id}">{label}</a>"##,
43 class = style.anchor_class,
44 link_class = ORBITAL_LINK_INLINE_CLASS,
45 id = id,
46 label = label,
47 )
48 })
49 .to_string()
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 #[test]
57 fn prepares_and_replaces_mention_links() {
58 let md = prepare_mention_markdown("Hi @[Jordan Lee](u1)!");
59 assert!(md.contains("mention-ref:u1"));
60 let html = "<p>Hi <a href=\"mention-ref:u1\">@Jordan Lee</a>!</p>".to_string();
61 let out = replace_mention_links(&html, MentionLinkStyle::history());
62 assert!(out.contains("data-mention-id=\"u1\""));
63 assert!(out.contains("orbital-history__mention-ref"));
64 assert!(out.contains("orbital-link orbital-link--inline"));
65 }
66}